Adding more plugins documentation
This commit is contained in:
parent
135212efcb
commit
ad1c87b2be
22 changed files with 1030 additions and 49 deletions
6
docs/source/platypush/plugins/gpio.sensor.distance.rst
Normal file
6
docs/source/platypush/plugins/gpio.sensor.distance.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.gpio.sensor.distance``
|
||||
==========================================
|
||||
|
||||
.. automodule:: platypush.plugins.gpio.sensor.distance
|
||||
:members:
|
||||
|
7
docs/source/platypush/plugins/gpio.sensor.mcp3008.rst
Normal file
7
docs/source/platypush/plugins/gpio.sensor.mcp3008.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
``platypush.plugins.gpio.sensor.mcp3008``
|
||||
=========================================
|
||||
|
||||
.. automodule:: platypush.plugins.gpio.sensor.mcp3008
|
||||
:members:
|
||||
|
||||
|
6
docs/source/platypush/plugins/gpio.sensor.rst
Normal file
6
docs/source/platypush/plugins/gpio.sensor.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.gpio.sensor``
|
||||
=================================
|
||||
|
||||
.. automodule:: platypush.plugins.gpio.sensor
|
||||
:members:
|
||||
|
6
docs/source/platypush/plugins/gpio.zeroborg.rst
Normal file
6
docs/source/platypush/plugins/gpio.zeroborg.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.gpio.zeroborg``
|
||||
===================================
|
||||
|
||||
.. automodule:: platypush.plugins.gpio.zeroborg
|
||||
:members:
|
||||
|
7
docs/source/platypush/plugins/http.request.rst
Normal file
7
docs/source/platypush/plugins/http.request.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
``platypush.plugins.http.request``
|
||||
==================================
|
||||
|
||||
.. automodule:: platypush.plugins.http.request
|
||||
:members:
|
||||
|
||||
|
6
docs/source/platypush/plugins/light.hue.rst
Normal file
6
docs/source/platypush/plugins/light.hue.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.light.hue``
|
||||
===============================
|
||||
|
||||
.. automodule:: platypush.plugins.light.hue
|
||||
:members:
|
||||
|
6
docs/source/platypush/plugins/light.rst
Normal file
6
docs/source/platypush/plugins/light.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.light``
|
||||
===========================
|
||||
|
||||
.. automodule:: platypush.plugins.light
|
||||
:members:
|
||||
|
6
docs/source/platypush/plugins/midi.rst
Normal file
6
docs/source/platypush/plugins/midi.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.midi``
|
||||
==========================
|
||||
|
||||
.. automodule:: platypush.plugins.midi
|
||||
:members:
|
||||
|
7
docs/source/platypush/plugins/mqtt.rst
Normal file
7
docs/source/platypush/plugins/mqtt.rst
Normal file
|
@ -0,0 +1,7 @@
|
|||
``platypush.plugins.mqtt``
|
||||
==========================
|
||||
|
||||
.. automodule:: platypush.plugins.mqtt
|
||||
:members:
|
||||
|
||||
|
6
docs/source/platypush/plugins/music.mpd.rst
Normal file
6
docs/source/platypush/plugins/music.mpd.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
``platypush.plugins.music.mpd``
|
||||
===============================
|
||||
|
||||
.. automodule:: platypush.plugins.music.mpd
|
||||
:members:
|
||||
|
|
@ -16,4 +16,14 @@ Plugins
|
|||
platypush/plugins/google.mail.rst
|
||||
platypush/plugins/google.maps.rst
|
||||
platypush/plugins/gpio.rst
|
||||
platypush/plugins/gpio.sensor.rst
|
||||
platypush/plugins/gpio.sensor.distance.rst
|
||||
platypush/plugins/gpio.sensor.mcp3008.rst
|
||||
platypush/plugins/gpio.zeroborg.rst
|
||||
platypush/plugins/http.request.rst
|
||||
platypush/plugins/light.rst
|
||||
platypush/plugins/light.hue.rst
|
||||
platypush/plugins/midi.rst
|
||||
platypush/plugins/mqtt.rst
|
||||
platypush/plugins/music.mpd.rst
|
||||
|
||||
|
|
|
@ -2,13 +2,36 @@ from platypush.plugins import Plugin
|
|||
|
||||
|
||||
class GpioSensorPlugin(Plugin):
|
||||
"""
|
||||
GPIO sensor abstract plugin. Any plugin that interacts with sensor via GPIO
|
||||
should implement this class (and the get_measurement() method)
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_measurement(self, *args, **kwargs):
|
||||
"""
|
||||
Implemented by the subclasses.
|
||||
|
||||
:returns: Either a raw scalar:
|
||||
|
||||
``output = 273.16``
|
||||
|
||||
or a name-value dictionary with the values that have been read::
|
||||
|
||||
output = {
|
||||
"temperature": 21.5,
|
||||
"humidity": 41.0
|
||||
}
|
||||
"""
|
||||
raise NotImplementedError('get_measurement should be implemented in a derived class')
|
||||
|
||||
def get_data(self, *args, **kwargs):
|
||||
"""
|
||||
Alias for ``get_measurement``
|
||||
"""
|
||||
|
||||
return self.get_measurement(*args, **kwargs)
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
|
@ -1,14 +1,30 @@
|
|||
import threading
|
||||
import time
|
||||
|
||||
import RPi.GPIO as gpio
|
||||
|
||||
from platypush.message.response import Response
|
||||
from platypush.plugins.gpio.sensor import GpioSensorPlugin
|
||||
|
||||
|
||||
class GpioSensorDistancePlugin(GpioSensorPlugin):
|
||||
"""
|
||||
You can use this plugin to interact with a distance sensor on your Raspberry
|
||||
Pi (tested with a HC-SR04 ultrasound sensor).
|
||||
|
||||
Requires:
|
||||
|
||||
* ``RPi.GPIO`` (``pip install RPi.GPIO``)
|
||||
"""
|
||||
|
||||
def __init__(self, trigger_pin, echo_pin, *args, **kwargs):
|
||||
"""
|
||||
:param trigger_pin: GPIO PIN where you connected your sensor trigger PIN (the one that triggers the sensor to perform a measurement).
|
||||
:type trigger_pin: int
|
||||
|
||||
:param echo_pin: GPIO PIN where you connected your sensor echo PIN (the one that will listen for the signal to bounce back and therefore trigger the distance calculation).
|
||||
:type echo_pin: int
|
||||
"""
|
||||
|
||||
import RPi.GPIO as gpio
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.trigger_pin = trigger_pin
|
||||
|
@ -24,6 +40,13 @@ class GpioSensorDistancePlugin(GpioSensorPlugin):
|
|||
|
||||
|
||||
def get_measurement(self):
|
||||
"""
|
||||
Extends :func:`.GpioSensorPlugin.get_measurement`
|
||||
|
||||
:returns: Distance measurement as a scalar (in mm):
|
||||
"""
|
||||
|
||||
import RPi.GPIO as gpio
|
||||
gpio.setmode(gpio.BCM)
|
||||
gpio.setup(self.trigger_pin, gpio.OUT)
|
||||
gpio.setup(self.echo_pin, gpio.IN)
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import enum
|
||||
import time
|
||||
|
||||
import Adafruit_GPIO.SPI as SPI
|
||||
import Adafruit_MCP3008
|
||||
|
||||
from platypush.plugins.gpio.sensor import GpioSensorPlugin
|
||||
from platypush.message.response import Response
|
||||
|
||||
|
@ -15,15 +12,78 @@ class MCP3008Mode(enum.Enum):
|
|||
|
||||
class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
||||
"""
|
||||
Plugin to read analog sensor values from an MCP3008 chipset,
|
||||
see https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/mcp3008
|
||||
Requires adafruit-mcp3008 Python package
|
||||
Plugin to read analog sensor values from an MCP3008 chipset. The MCP3008
|
||||
chipset is a circuit that allows you to read measuremnts from multiple
|
||||
analog sources (e.g. sensors) and multiplex them to a digital device like a
|
||||
Raspberry Pi or a regular laptop. See
|
||||
https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/mcp3008
|
||||
for more info.
|
||||
|
||||
Requires:
|
||||
|
||||
* ``adafruit-mcp3008`` (``pip install adafruit-mcp3008``)
|
||||
"""
|
||||
|
||||
N_CHANNELS = 8
|
||||
|
||||
def __init__(self, CLK=None, MISO=None, MOSI=None, CS=None, spi_port=None,
|
||||
spi_device=None, channels=None, Vdd=3.3, *args, **kwargs):
|
||||
"""
|
||||
The MCP3008 can be connected in two modes:
|
||||
|
||||
* Hardware SPI mode: advised if you have enough GPIO pins available
|
||||
(and slightly faster)
|
||||
* Software SPI mode: useful if you don't have all the required GPIO
|
||||
PINs for hardware SPI available. Slightly slower, as the conversion
|
||||
is done via software, but still relatively performant.
|
||||
|
||||
See
|
||||
https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/mcp3008#wiring
|
||||
for info
|
||||
|
||||
:param CLK: (software SPI mode) CLK GPIO PIN
|
||||
:type CLK: int
|
||||
:param MISO: (software SPI mode) MISO GPIO PIN
|
||||
:type MISO: int
|
||||
:param MOSI: (software SPI mode) MOSI GPIO PIN
|
||||
:type MOSI: int
|
||||
:param CS: (software SPI mode) CS GPIO PIN
|
||||
:type CS: int
|
||||
|
||||
:param spi_port: (hardware SPI mode) SPI port
|
||||
:type spi_port: int
|
||||
:param spi_device: (hardware SPI mode) SPI device name
|
||||
:type spi_device: str
|
||||
|
||||
:param channels: name-value mapping between MCP3008 output PINs and sensor names. This mapping will be used when you get values through :func:`.get_measurement()`.
|
||||
Example::
|
||||
|
||||
channels = {
|
||||
"0": {
|
||||
"name": "temperature",
|
||||
"conv_function": 'round(x*100.0, 2)' # T = Vout / (10 [mV/C])
|
||||
},
|
||||
"1": {
|
||||
"name": "light", # ALS-PT9
|
||||
"conv_function": 'round(x*1000.0, 6)' # ALS-PT9 has a 10 kOhm resistor
|
||||
}
|
||||
}
|
||||
|
||||
Note that you can also pass a conversion function as
|
||||
``conv_function`` that will convert the output voltage to whichever
|
||||
human-readable value you wish. In the case above I connected a
|
||||
simple temperature sensor to the channel 0 and a simple ALS-PT9
|
||||
light sensor to the channel 1, and passed the appropriate conversion
|
||||
functions to convert from voltage to, respectively, temperature in
|
||||
Celsius degrees and light intensity in lumen. Note that we reference
|
||||
the current voltage as ``x`` in ``conv_function``.
|
||||
|
||||
:type channels: dict
|
||||
|
||||
:param Vdd: Input voltage provided to the circuit (default: 3.3V, Raspberry Pi default power source)
|
||||
:type Vdd: float
|
||||
"""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if CLK and MISO and MOSI and CS:
|
||||
|
@ -47,6 +107,9 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
|||
|
||||
|
||||
def _get_mcp(self):
|
||||
import Adafruit_GPIO.SPI as SPI
|
||||
import Adafruit_MCP3008
|
||||
|
||||
if self.mode == MCP3008Mode.SOFTWARE:
|
||||
self.mcp = Adafruit_MCP3008.MCP3008(clk=self.CLK, cs=self.CS,
|
||||
miso=self.MISO, mosi=self.MOSI)
|
||||
|
@ -63,6 +126,27 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
|||
|
||||
|
||||
def get_measurement(self):
|
||||
"""
|
||||
Returns a measurement from the sensors connected to the MCP3008 device.
|
||||
If channels were passed to the configuration, the appropriate sensor names
|
||||
will be used and the voltage will be converted through the appropriate
|
||||
conversion function. Example::
|
||||
|
||||
output = {
|
||||
"temperature": 21.0, # Celsius
|
||||
"humidity": 45.1 # %
|
||||
}
|
||||
|
||||
Otherwise, the output dictionary will contain the channel numbers as key
|
||||
and the row voltage (between 0 and 255) will be returned. Example::
|
||||
|
||||
output = {
|
||||
"0": 145,
|
||||
"1": 130
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
mcp = self._get_mcp()
|
||||
values = {}
|
||||
|
||||
|
|
|
@ -18,12 +18,65 @@ class Direction(enum.Enum):
|
|||
|
||||
|
||||
class GpioZeroborgPlugin(Plugin):
|
||||
"""
|
||||
ZeroBorg plugin. It allows you to control a ZeroBorg
|
||||
(https://www.piborg.org/motor-control-1135/zeroborg) motor controller and
|
||||
infrared sensor circuitry for Raspberry Pi
|
||||
"""
|
||||
|
||||
_drive_thread = None
|
||||
_can_run = False
|
||||
_direction = None
|
||||
|
||||
|
||||
def __init__(self, directions = {}, *args, **kwargs):
|
||||
"""
|
||||
:param directions: Configuration for the motor directions. A direction is basically a configuration of the power delivered to each motor to allow whichever object you're controlling (wheels, robotic arms etc.) to move in a certain direction. In my experience the ZeroBorg always needs a bit of calibration, depending on factory defaults and the mechanical properties of the load it controls.
|
||||
Example configuration that I use to control a simple 4WD robot::
|
||||
|
||||
directions = {
|
||||
"up": {
|
||||
"motor_1_power": 0.4821428571428572,
|
||||
"motor_2_power": 0.4821428571428572,
|
||||
"motor_3_power": -0.6707142857142858,
|
||||
"motor_4_power": -0.6707142857142858
|
||||
},
|
||||
"down": {
|
||||
"motor_1_power": -0.4821428571428572,
|
||||
"motor_2_power": -0.4821428571428572,
|
||||
"motor_3_power": 0.825,
|
||||
"motor_4_power": 0.825
|
||||
},
|
||||
"left": {
|
||||
"motor_1_power": -0.1392857142857143,
|
||||
"motor_2_power": -0.1392857142857143,
|
||||
"motor_3_power": -1.0553571428571429,
|
||||
"motor_4_power": -1.0553571428571429
|
||||
},
|
||||
"right": {
|
||||
"motor_1_power": 1.0017857142857143,
|
||||
"motor_2_power": 1.0017857142857143,
|
||||
"motor_3_power": 0.6214285714285713,
|
||||
"motor_4_power": 0.6214285714285713
|
||||
},
|
||||
"auto": {
|
||||
"sensors": [
|
||||
{
|
||||
"plugin": "gpio.sensor.distance",
|
||||
"threshold": 400.0,
|
||||
"timeout": 2.0,
|
||||
"above_threshold_direction": "up",
|
||||
"below_threshold_direction": "left"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Note that the special direction "auto" can contain a configuration that allows your device to move autonomously based on the inputs it gets from some sensors. In this case, I set the sensors configuration (a list) to periodically poll a GPIO-based ultrasound distance sensor plugin. ``timeout`` says after how long a poll attempt should fail. The plugin package is specified through ``plugin`` (``gpio.sensor.distance``) in this case, note that the plugin must be configured as well in order to work). The ``threshold`` value says around which value your logic should trigger. In this case, threshold=400 (40 cm). When the distance value is above that threshold (``above_threshold_direction``), then go "up" (no obstacles ahead). Otherwise (``below_threshold_direction``), turn "left" (avoid the obstacle).
|
||||
|
||||
:type directions: dict
|
||||
"""
|
||||
|
||||
import platypush.plugins.gpio.zeroborg.lib as ZeroBorg
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
@ -75,6 +128,16 @@ class GpioZeroborgPlugin(Plugin):
|
|||
|
||||
|
||||
def drive(self, direction):
|
||||
"""
|
||||
Drive the motors in a certain direction.
|
||||
|
||||
:param direction: Direction name (note: it must be a configured direction). Special directions:
|
||||
* ``auto`` - Enter automatic drive mode
|
||||
* ``auto_toggle`` - Toggle automatic drive mode (on or off)
|
||||
* ``stop`` - Turn off the motors
|
||||
|
||||
"""
|
||||
|
||||
prev_direction = self._direction
|
||||
|
||||
self._can_run = True
|
||||
|
@ -125,6 +188,10 @@ class GpioZeroborgPlugin(Plugin):
|
|||
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Turns off the motors
|
||||
"""
|
||||
|
||||
self._can_run = False
|
||||
if self._drive_thread and threading.get_ident() != self._drive_thread.ident:
|
||||
self._drive_thread.join()
|
||||
|
|
|
@ -5,7 +5,41 @@ from platypush.message.response import Response
|
|||
from platypush.plugins import Plugin
|
||||
|
||||
class HttpRequestPlugin(Plugin):
|
||||
""" Plugin for executing custom HTTP requests """
|
||||
"""
|
||||
Plugin for executing custom HTTP requests.
|
||||
|
||||
Requires:
|
||||
|
||||
* **requests** (``pip install requests``)
|
||||
|
||||
Some example usages::
|
||||
|
||||
# Execute a GET request on a JSON endpoint
|
||||
{
|
||||
"type": "request",
|
||||
"action": "http.request.get",
|
||||
"args": {
|
||||
"url": "http://remote-host/api/v1/entity",
|
||||
"params": {
|
||||
"start": "2000-01-01"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Execute an action on another Platypush host through HTTP interface
|
||||
{
|
||||
"type": "request",
|
||||
"action": "http.request.post",
|
||||
"args": {
|
||||
"url": "http://remote-host:8008/execute",
|
||||
"json": {
|
||||
"type": "request",
|
||||
"target": "remote-host",
|
||||
"action": "music.mpd.play"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def _exec(self, method, url, output='text', **kwargs):
|
||||
""" Available output types: text (default), json, binary """
|
||||
|
@ -21,26 +55,86 @@ class HttpRequestPlugin(Plugin):
|
|||
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
"""
|
||||
Perform a GET request
|
||||
|
||||
:param url: Target URL
|
||||
:type url: str
|
||||
|
||||
:param kwargs: Additional arguments that will be transparently provided to the ``requests`` object, including but not limited to query params, data, JSON, headers etc. (see http://docs.python-requests.org/en/master/user/quickstart/#make-a-request)
|
||||
:type kwargs: dict
|
||||
"""
|
||||
|
||||
return self._exec(method='get', url=url, **kwargs)
|
||||
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
"""
|
||||
Perform a POST request
|
||||
|
||||
:param url: Target URL
|
||||
:type url: str
|
||||
|
||||
:param kwargs: Additional arguments that will be transparently provided to the ``requests`` object, including but not limited to query params, data, JSON, headers etc. (see http://docs.python-requests.org/en/master/user/quickstart/#make-a-request)
|
||||
:type kwargs: dict
|
||||
"""
|
||||
|
||||
return self._exec(method='post', url=url, **kwargs)
|
||||
|
||||
|
||||
def head(self, url, **kwargs):
|
||||
"""
|
||||
Perform an HTTP HEAD request
|
||||
|
||||
:param url: Target URL
|
||||
:type url: str
|
||||
|
||||
:param kwargs: Additional arguments that will be transparently provided to the ``requests`` object, including but not limited to query params, data, JSON, headers etc. (see http://docs.python-requests.org/en/master/user/quickstart/#make-a-request)
|
||||
:type kwargs: dict
|
||||
"""
|
||||
|
||||
return self._exec(method='head', url=url, **kwargs)
|
||||
|
||||
|
||||
def put(self, url, **kwargs):
|
||||
"""
|
||||
Perform a PUT request
|
||||
|
||||
:param url: Target URL
|
||||
:type url: str
|
||||
|
||||
:param kwargs: Additional arguments that will be transparently provided to the ``requests`` object, including but not limited to query params, data, JSON, headers etc. (see http://docs.python-requests.org/en/master/user/quickstart/#make-a-request)
|
||||
:type kwargs: dict
|
||||
"""
|
||||
|
||||
return self._exec(method='put', url=url, **kwargs)
|
||||
|
||||
|
||||
def delete(self, url, **kwargs):
|
||||
"""
|
||||
Perform a DELETE request
|
||||
|
||||
:param url: Target URL
|
||||
:type url: str
|
||||
|
||||
:param kwargs: Additional arguments that will be transparently provided to the ``requests`` object, including but not limited to query params, data, JSON, headers etc. (see http://docs.python-requests.org/en/master/user/quickstart/#make-a-request)
|
||||
:type kwargs: dict
|
||||
"""
|
||||
|
||||
return self._exec(method='delete', url=url, **kwargs)
|
||||
|
||||
|
||||
def options(self, url, **kwargs):
|
||||
"""
|
||||
Perform an HTTP OPTIONS request
|
||||
|
||||
:param url: Target URL
|
||||
:type url: str
|
||||
|
||||
:param kwargs: Additional arguments that will be transparently provided to the ``requests`` object, including but not limited to query params, data, JSON, headers etc. (see http://docs.python-requests.org/en/master/user/quickstart/#make-a-request)
|
||||
:type kwargs: dict
|
||||
"""
|
||||
|
||||
return self._exec(method='options', url=url, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,30 @@ from platypush.message.response import Response
|
|||
from .. import Plugin
|
||||
|
||||
class LastfmPlugin(Plugin):
|
||||
"""
|
||||
Plugin to interact with your Last.FM (https://last.fm) account, update your
|
||||
current track and your scrobbles.
|
||||
|
||||
Requires:
|
||||
|
||||
* **pylast** (``pip install pylast``)
|
||||
"""
|
||||
|
||||
def __init__(self, api_key, api_secret, username, password):
|
||||
"""
|
||||
:param api_key: Last.FM API key, see https://www.last.fm/api
|
||||
:type api_key: str
|
||||
|
||||
:param api_secret: Last.FM API secret, see https://www.last.fm/api
|
||||
:type api_key: str
|
||||
|
||||
:param username: Last.FM username
|
||||
:type api_key: str
|
||||
|
||||
:param password: Last.FM password, used to sign the requests
|
||||
:type api_key: str
|
||||
"""
|
||||
|
||||
self.api_key = api_key
|
||||
self.api_secret = api_secret
|
||||
self.username = username
|
||||
|
@ -20,6 +43,17 @@ class LastfmPlugin(Plugin):
|
|||
|
||||
|
||||
def scrobble(self, artist, title, album=None, **kwargs):
|
||||
"""
|
||||
Scrobble a track to Last.FM
|
||||
|
||||
:param artist: Artist
|
||||
:type artist: str
|
||||
:param title: Title
|
||||
:type title: str
|
||||
:param album: Album (optional)
|
||||
:type album: str
|
||||
"""
|
||||
|
||||
self.lastfm.scrobble(
|
||||
artist = artist,
|
||||
title = title,
|
||||
|
@ -31,6 +65,17 @@ class LastfmPlugin(Plugin):
|
|||
|
||||
|
||||
def update_now_playing(self, artist, title, album=None, **kwargs):
|
||||
"""
|
||||
Update the currently playing track
|
||||
|
||||
:param artist: Artist
|
||||
:type artist: str
|
||||
:param title: Title
|
||||
:type title: str
|
||||
:param album: Album (optional)
|
||||
:type album: str
|
||||
"""
|
||||
|
||||
self.lastfm.update_now_playing(
|
||||
artist = artist,
|
||||
title = title,
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
from .. import Plugin
|
||||
|
||||
class LightPlugin(Plugin):
|
||||
"""
|
||||
Abstract plugin to interface your logic with lights/bulbs.
|
||||
"""
|
||||
|
||||
def on(self):
|
||||
""" Turn the light on """
|
||||
raise NotImplementedError()
|
||||
|
||||
def off(self):
|
||||
""" Turn the light off """
|
||||
raise NotImplementedError()
|
||||
|
||||
def toggle(self):
|
||||
""" Toggle the light status (on/off) """
|
||||
raise NotImplementedError()
|
||||
|
||||
def status(self):
|
||||
""" Get the light status """
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,13 @@ from platypush.message.response import Response
|
|||
from .. import LightPlugin
|
||||
|
||||
class LightHuePlugin(LightPlugin):
|
||||
""" Philips Hue lights plugin """
|
||||
"""
|
||||
Philips Hue lights plugin.
|
||||
|
||||
Requires:
|
||||
|
||||
* **phue** (``pip install phue``)
|
||||
"""
|
||||
|
||||
MAX_BRI = 255
|
||||
MAX_SAT = 255
|
||||
|
@ -31,11 +37,14 @@ class LightHuePlugin(LightPlugin):
|
|||
|
||||
def __init__(self, bridge, lights=None, groups=None):
|
||||
"""
|
||||
Constructor
|
||||
Params:
|
||||
bridge -- Bridge address or hostname
|
||||
lights -- Lights to be controlled (default: all)
|
||||
groups -- Groups to be controlled (default: all)
|
||||
:param bridge: Bridge address or hostname
|
||||
:type bridge: str
|
||||
|
||||
:param lights: Default lights to be controlled (default: all)
|
||||
:type lights: list[str]
|
||||
|
||||
:param groups Default groups to be controlled (default: all)
|
||||
:type groups: list[str]
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
|
@ -66,6 +75,14 @@ class LightHuePlugin(LightPlugin):
|
|||
self.lights.extend([l.name for l in g.lights])
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
Connect to the configured Hue bridge. If the device hasn't been paired
|
||||
yet, uncomment the ``.connect()`` and ``.get_api()`` lines and retry
|
||||
after clicking the pairing button on your bridge.
|
||||
|
||||
:todo: Support for dynamic retry and better user interaction in case of bridge pairing neeeded.
|
||||
"""
|
||||
|
||||
# Lazy init
|
||||
if not self.bridge:
|
||||
self.bridge = Bridge(self.bridge_address)
|
||||
|
@ -83,14 +100,127 @@ class LightHuePlugin(LightPlugin):
|
|||
|
||||
|
||||
def get_scenes(self):
|
||||
"""
|
||||
Get the available scenes on the devices.
|
||||
|
||||
:returns: The scenes configured on the bridge.
|
||||
|
||||
Example output::
|
||||
|
||||
output = {
|
||||
"scene-id-1": {
|
||||
"name": "Scene 1",
|
||||
"lights": [
|
||||
"1",
|
||||
"3"
|
||||
],
|
||||
"owner": "owner-id",
|
||||
"recycle": true,
|
||||
"locked": false,
|
||||
"appdata": {},
|
||||
"picture": "",
|
||||
"lastupdated": "2018-06-01T00:00:00",
|
||||
"version": 1
|
||||
},
|
||||
"scene-id-2": {
|
||||
# ...
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
return Response(output=self.bridge.get_scene())
|
||||
|
||||
|
||||
def get_lights(self):
|
||||
"""
|
||||
Get the configured lights.
|
||||
|
||||
:returns: List of available lights as id->dict.
|
||||
|
||||
Example::
|
||||
|
||||
output = {
|
||||
"1": {
|
||||
"state": {
|
||||
"on": true,
|
||||
"bri": 254,
|
||||
"hue": 1532,
|
||||
"sat": 215,
|
||||
"effect": "none",
|
||||
"xy": [
|
||||
0.6163,
|
||||
0.3403
|
||||
],
|
||||
"ct": 153,
|
||||
"alert": "none",
|
||||
"colormode": "hs",
|
||||
"reachable": true
|
||||
},
|
||||
"type": "Extended color light",
|
||||
"name": "Lightbulb 1",
|
||||
"modelid": "LCT001",
|
||||
"manufacturername": "Philips",
|
||||
"uniqueid": "00:11:22:33:44:55:66:77-88",
|
||||
"swversion": "5.105.0.21169"
|
||||
},
|
||||
"2": {
|
||||
# ...
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
return Response(output=self.bridge.get_light())
|
||||
|
||||
|
||||
def get_groups(self):
|
||||
"""
|
||||
Get the list of configured light groups.
|
||||
|
||||
:returns: List of configured light groups as id->dict.
|
||||
|
||||
Example::
|
||||
|
||||
output = {
|
||||
"1": {
|
||||
"name": "Living Room",
|
||||
"lights": [
|
||||
"16",
|
||||
"13",
|
||||
"12",
|
||||
"11",
|
||||
"10",
|
||||
"9",
|
||||
"1",
|
||||
"3"
|
||||
],
|
||||
"type": "Room",
|
||||
"state": {
|
||||
"all_on": true,
|
||||
"any_on": true
|
||||
},
|
||||
"class": "Living room",
|
||||
"action": {
|
||||
"on": true,
|
||||
"bri": 241,
|
||||
"hue": 37947,
|
||||
"sat": 221,
|
||||
"effect": "none",
|
||||
"xy": [
|
||||
0.2844,
|
||||
0.2609
|
||||
],
|
||||
"ct": 153,
|
||||
"alert": "none",
|
||||
"colormode": "hs"
|
||||
}
|
||||
},
|
||||
|
||||
"2": {
|
||||
# ...
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
return Response(output=self.bridge.get_group())
|
||||
|
||||
|
||||
|
@ -129,40 +259,132 @@ class LightHuePlugin(LightPlugin):
|
|||
return Response(output='ok')
|
||||
|
||||
def set_light(self, light, **kwargs):
|
||||
"""
|
||||
Set a light (or lights) property.
|
||||
|
||||
:param light: Light or lights to set. Can be a string representing the light name, a light object, a list of string, or a list of light objects.
|
||||
:param kwargs: key-value list of parameters to set.
|
||||
|
||||
Example call::
|
||||
|
||||
{
|
||||
"type": "request",
|
||||
"target": "hostname",
|
||||
"action": "light.hue.set_light",
|
||||
"args": {
|
||||
"light": "Bulb 1",
|
||||
"sat": 255
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
self.connect()
|
||||
self.bridge.set_light(light, **kwargs)
|
||||
return Response(output='ok')
|
||||
|
||||
def set_group(self, group, **kwargs):
|
||||
"""
|
||||
Set a group (or groups) property.
|
||||
|
||||
:param group: Group or groups to set. Can be a string representing the group name, a group object, a list of strings, or a list of group objects.
|
||||
:param kwargs: key-value list of parameters to set.
|
||||
|
||||
Example call::
|
||||
|
||||
{
|
||||
"type": "request",
|
||||
"target": "hostname",
|
||||
"action": "light.hue.set_group",
|
||||
"args": {
|
||||
"light": "Living Room",
|
||||
"sat": 255
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
self.connect()
|
||||
self.bridge.set_group(group, **kwargs)
|
||||
return Response(output='ok')
|
||||
|
||||
def on(self, lights=[], groups=[]):
|
||||
"""
|
||||
Turn lights/groups on.
|
||||
|
||||
:param lights: Lights to turn on (names or light objects). Default: plugin default lights
|
||||
:param groups: Groups to turn on (names or group objects). Default: plugin default groups
|
||||
"""
|
||||
|
||||
return self._exec('on', True, lights=lights, groups=groups)
|
||||
|
||||
def off(self, lights=[], groups=[]):
|
||||
"""
|
||||
Turn lights/groups off.
|
||||
|
||||
:param lights: Lights to turn off (names or light objects). Default: plugin default lights
|
||||
:param groups: Groups to turn off (names or group objects). Default: plugin default groups
|
||||
"""
|
||||
|
||||
return self._exec('on', False, lights=lights, groups=groups)
|
||||
|
||||
def bri(self, value, lights=[], groups=[]):
|
||||
"""
|
||||
Set lights/groups brightness.
|
||||
|
||||
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
||||
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
||||
:param value: Brightness value (range: 0-255)
|
||||
"""
|
||||
|
||||
return self._exec('bri', int(value) % (self.MAX_BRI+1),
|
||||
lights=lights, groups=groups)
|
||||
|
||||
def sat(self, value, lights=[], groups=[]):
|
||||
"""
|
||||
Set lights/groups saturation.
|
||||
|
||||
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
||||
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
||||
:param value: Saturation value (range: 0-255)
|
||||
"""
|
||||
|
||||
return self._exec('sat', int(value) % (self.MAX_SAT+1),
|
||||
lights=lights, groups=groups)
|
||||
|
||||
def hue(self, value, lights=[], groups=[]):
|
||||
"""
|
||||
Set lights/groups color hue.
|
||||
|
||||
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
||||
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
||||
:param value: Hue value (range: 0-65535)
|
||||
"""
|
||||
|
||||
return self._exec('hue', int(value) % (self.MAX_HUE+1),
|
||||
lights=lights, groups=groups)
|
||||
|
||||
def scene(self, name, lights=[], groups=[]):
|
||||
"""
|
||||
Set a scene by name.
|
||||
|
||||
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
||||
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
||||
:param name: Name of the scene
|
||||
"""
|
||||
|
||||
return self._exec('scene', name=name, lights=lights, groups=groups)
|
||||
|
||||
def is_animation_running(self):
|
||||
"""
|
||||
:returns: True if there is an animation running, false otherwise.
|
||||
"""
|
||||
|
||||
return self.animation_thread is not None
|
||||
|
||||
def stop_animation(self):
|
||||
"""
|
||||
Stop a running animation if any
|
||||
"""
|
||||
|
||||
if self.animation_thread and self.animation_thread.is_alive():
|
||||
self.redis.rpush(self.ANIMATION_CTRL_QUEUE_NAME, 'STOP')
|
||||
|
||||
|
@ -170,6 +392,40 @@ class LightHuePlugin(LightPlugin):
|
|||
hue_range=[0, MAX_HUE], sat_range=[0, MAX_SAT],
|
||||
bri_range=[MAX_BRI-1, MAX_BRI], lights=None, groups=None,
|
||||
hue_step=1000, sat_step=2, bri_step=1, transition_seconds=1.0):
|
||||
"""
|
||||
Run a lights animation.
|
||||
|
||||
:param animation: Animation name. Supported types: **color_transition** and **blink**
|
||||
:type animation: str
|
||||
|
||||
:param duration: Animation duration in seconds (default: None, i.e. continue until stop)
|
||||
:type duration: float
|
||||
|
||||
:param hue_range: If you selected a color transition, this will specify the hue range of your color transition. Default: [0, 65535]
|
||||
:type hue_range: list[int]
|
||||
|
||||
:param sat_range: If you selected a color transition, this will specify the saturation range of your color transition. Default: [0, 255]
|
||||
:type sat_range: list[int]
|
||||
|
||||
:param bri_range: If you selected a color transition, this will specify the brightness range of your color transition. Default: [254, 255]
|
||||
:type bri_range: list[int]
|
||||
|
||||
:param lights: Lights to control (names or light objects). Default: plugin default lights
|
||||
:param groups: Groups to control (names or group objects). Default: plugin default groups
|
||||
|
||||
:param hue_step: If you selected a color transition, this will specify by how much the color hue will change between iterations. Default: 1000
|
||||
:type hue_step: int
|
||||
|
||||
:param sat_step: If you selected a color transition, this will specify by how much the saturation will change between iterations. Default: 2
|
||||
:type sat_step: int
|
||||
|
||||
:param bri_step: If you selected a color transition, this will specify by how much the brightness will change between iterations. Default: 1
|
||||
:type bri_step: int
|
||||
|
||||
:param transition_seconds: Time between two transitions or blinks in seconds. Default: 1.0
|
||||
:type treansition_seconds: float
|
||||
"""
|
||||
|
||||
|
||||
def _initialize_light_attrs(lights):
|
||||
if animation == self.Animation.COLOR_TRANSITION:
|
||||
|
|
|
@ -7,14 +7,23 @@ from platypush.plugins import Plugin
|
|||
|
||||
class MidiPlugin(Plugin):
|
||||
"""
|
||||
Virtual MIDI controller plugin.
|
||||
It requires python-rtmidi - https://pypi.org/project/python-rtmidi/
|
||||
Virtual MIDI controller plugin. It allows you to send custom MIDI messages
|
||||
to any connected devices.
|
||||
|
||||
Requires:
|
||||
|
||||
* **python-rtmidi** (``pip install python-rtmidi``)
|
||||
"""
|
||||
|
||||
_played_notes = set()
|
||||
|
||||
def __init__(self, device_name='Platypush virtual MIDI output',
|
||||
*args, **kwargs):
|
||||
"""
|
||||
:param device_name: MIDI virtual device name (default: *Platypush virtual MIDI output*)
|
||||
:type device_name: str
|
||||
"""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.device_name = device_name
|
||||
|
@ -32,30 +41,32 @@ class MidiPlugin(Plugin):
|
|||
|
||||
def send_message(self, values, *args, **kwargs):
|
||||
"""
|
||||
Values is expected to be a list containing the MIDI command code and
|
||||
the command parameters - see reference at
|
||||
https://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html
|
||||
:param values: Values is expected to be a list containing the MIDI command code and the command parameters - see reference at https://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html
|
||||
:type values: list[int]
|
||||
|
||||
Available MIDI commands:
|
||||
0x80 Note Off
|
||||
0x90 Note On
|
||||
0xA0 Aftertouch
|
||||
0xB0 Continuous controller
|
||||
0xC0 Patch change
|
||||
0xD0 Channel Pressure
|
||||
0xE0 Pitch bend
|
||||
0xF0 Start of system exclusive message
|
||||
0xF1 MIDI Time Code Quarter Frame (Sys Common)
|
||||
0xF2 Song Position Pointer (Sys Common)
|
||||
0xF3 Song Select
|
||||
0xF6 Tune Request (Sys Common)
|
||||
0xF7 End of system exclusive message
|
||||
0xF8 Timing Clock (Sys Realtime)
|
||||
0xFA Start (Sys Realtime)
|
||||
0xFB Continue (Sys Realtime)
|
||||
0xFC Stop (Sys Realtime)
|
||||
0xFE Active Sensing (Sys Realtime)
|
||||
0xFF System Reset (Sys Realtime)
|
||||
* ``0x80`` Note Off
|
||||
* ``0x90`` Note On
|
||||
* ``0xA0`` Aftertouch
|
||||
* ``0xB0`` Continuous controller
|
||||
* ``0xC0`` Patch change
|
||||
* ``0xD0`` Channel Pressure
|
||||
* ``0xE0`` Pitch bend
|
||||
* ``0xF0`` Start of system exclusive message
|
||||
* ``0xF1`` MIDI Time Code Quarter Frame (Sys Common)
|
||||
* ``0xF2`` Song Position Pointer (Sys Common)
|
||||
* ``0xF3`` Song Select
|
||||
* ``0xF6`` Tune Request (Sys Common)
|
||||
* ``0xF7`` End of system exclusive message
|
||||
* ``0xF8`` Timing Clock (Sys Realtime)
|
||||
* ``0xFA`` Start (Sys Realtime)
|
||||
* ``0xFB`` Continue (Sys Realtime)
|
||||
* ``0xFC`` Stop (Sys Realtime)
|
||||
* ``0xFE`` Active Sensing (Sys Realtime)
|
||||
* ``0xFF`` System Reset (Sys Realtime)
|
||||
|
||||
:param args: Extra args that will be passed to ``rtmidi.send_message``
|
||||
:param kwargs: Extra kwargs that will be passed to ``rtmidi.send_message``
|
||||
"""
|
||||
|
||||
self.midiout.send_message(values, *args, **kwargs)
|
||||
|
@ -64,11 +75,16 @@ class MidiPlugin(Plugin):
|
|||
|
||||
def play_note(self, note, velocity, duration=0):
|
||||
"""
|
||||
Params:
|
||||
- note: MIDI note in range 0-127 with #60 = C4
|
||||
- velocity: MIDI note velocity in range 0-127
|
||||
- duration: Note duration in (float) seconds. Pass 0 if you don't
|
||||
want the note to get off
|
||||
Play a note with selected velocity and duration.
|
||||
|
||||
:param note: MIDI note in range 0-127 with #60 = C4
|
||||
:type note: int
|
||||
|
||||
:param velocity: MIDI note velocity in range 0-127
|
||||
:type velocity: int
|
||||
|
||||
:param duration: Note duration in seconds. Pass 0 if you don't want the note to get off
|
||||
:type duration: float
|
||||
"""
|
||||
|
||||
self.send_message([0x90, note, velocity]) # Note on
|
||||
|
@ -81,14 +97,26 @@ class MidiPlugin(Plugin):
|
|||
|
||||
|
||||
def release_note(self, note):
|
||||
"""
|
||||
Release a played note.
|
||||
|
||||
:param note: MIDI note in range 0-127 with #60 = C4
|
||||
:type note: int
|
||||
"""
|
||||
|
||||
self.send_message([0x80, note, 0]) # Note off
|
||||
self._played_notes.remove(note)
|
||||
|
||||
|
||||
def release_all_notes(self):
|
||||
"""
|
||||
Release all the notes being played.
|
||||
"""
|
||||
|
||||
played_notes = self._played_notes.copy()
|
||||
for note in played_notes:
|
||||
self.release_note(note)
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
||||
|
|
|
@ -7,7 +7,27 @@ from platypush.plugins import Plugin
|
|||
|
||||
|
||||
class MqttPlugin(Plugin):
|
||||
"""
|
||||
This plugin allows you to send custom message to a message queue compatible
|
||||
with the MQTT protocol, see http://mqtt.org/
|
||||
"""
|
||||
|
||||
def send_message(self, topic, msg, host, port=1883, *args, **kwargs):
|
||||
"""
|
||||
Sends a message to a topic/channel.
|
||||
|
||||
:param topic: Topic/channel where the message will be delivered
|
||||
:type topic: str
|
||||
|
||||
:param msg: Message to be sent. It can be a list, a dict, or a Message object
|
||||
|
||||
:param host: MQTT broker hostname/IP
|
||||
:type host: str
|
||||
|
||||
:param port: MQTT broker port (default: 1883)
|
||||
:type port: int
|
||||
"""
|
||||
|
||||
try: msg = json.dumps(msg)
|
||||
except: pass
|
||||
|
||||
|
|
|
@ -6,12 +6,27 @@ from platypush.message.response import Response
|
|||
from .. import MusicPlugin
|
||||
|
||||
class MusicMpdPlugin(MusicPlugin):
|
||||
def __init__(self, host, port):
|
||||
"""
|
||||
This plugin allows you to interact with an MPD/Mopidy music server. MPD
|
||||
(https://www.musicpd.org/) is a flexible server-side protocol/application
|
||||
for handling music collections and playing music, mostly aimed to manage
|
||||
local libraries. Mopidy (https://www.mopidy.com/) is an evolution of MPD,
|
||||
compatible with the original protocol and with support for multiple music
|
||||
sources through plugins (e.g. Spotify, TuneIn, Soundcloud, local files
|
||||
etc.).
|
||||
|
||||
Requires:
|
||||
|
||||
* **python-mpd2** (``pip install python-mpd2``)
|
||||
"""
|
||||
|
||||
def __init__(self, host, port=6600):
|
||||
"""
|
||||
Constructor
|
||||
Params:
|
||||
host -- MPD host
|
||||
port -- MPD port
|
||||
:param host: MPD IP/hostname
|
||||
:type host: str
|
||||
|
||||
:param port: MPD port (default: 6600)
|
||||
:type port: int
|
||||
"""
|
||||
|
||||
self.host = host
|
||||
|
@ -24,50 +39,95 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
return self.status()
|
||||
|
||||
def play(self, resource=None):
|
||||
"""
|
||||
Play a resource by path/URI
|
||||
|
||||
:param resource: Resource path/URI
|
||||
:type resource: str
|
||||
"""
|
||||
|
||||
if resource:
|
||||
self.clear()
|
||||
self.add(resource)
|
||||
return self._exec('play')
|
||||
|
||||
def play_pos(self, pos):
|
||||
"""
|
||||
Play a track in the current playlist by position number
|
||||
|
||||
:param pos: Position number
|
||||
:type resource: int
|
||||
"""
|
||||
|
||||
return self._exec('play', pos)
|
||||
|
||||
def pause(self):
|
||||
""" Pause playback """
|
||||
|
||||
status = self.status().output['state']
|
||||
if status == 'play': return self._exec('pause')
|
||||
else: return self._exec('play')
|
||||
|
||||
def pause_if_playing(self):
|
||||
""" Pause playback only if it's playing """
|
||||
|
||||
status = self.status().output['state']
|
||||
if status == 'play': return self._exec('pause')
|
||||
else: return Response(output={})
|
||||
|
||||
def play_if_paused(self):
|
||||
""" Play only if it's paused (resume) """
|
||||
|
||||
status = self.status().output['state']
|
||||
if status == 'pause': return self._exec('play')
|
||||
else: return Response(output={})
|
||||
|
||||
def stop(self):
|
||||
""" Stop playback """
|
||||
|
||||
return self._exec('stop')
|
||||
|
||||
def play_or_stop(self):
|
||||
""" Play or stop (play state toggle) """
|
||||
status = self.status().output['state']
|
||||
if status == 'play': return self._exec('stop')
|
||||
else: return self._exec('play')
|
||||
|
||||
def playid(self, track_id):
|
||||
"""
|
||||
Play a track by ID
|
||||
|
||||
:param track_id: Track ID
|
||||
:type track_id: str
|
||||
"""
|
||||
|
||||
return self._exec('playid', track_id)
|
||||
|
||||
def next(self):
|
||||
""" Play the next track """
|
||||
return self._exec('next')
|
||||
|
||||
def previous(self):
|
||||
""" Play the previous track """
|
||||
return self._exec('previous')
|
||||
|
||||
def setvol(self, vol):
|
||||
"""
|
||||
Set the volume
|
||||
|
||||
:param vol: Volume value (range: 0-100)
|
||||
:type vol: int
|
||||
"""
|
||||
return self._exec('setvol', vol)
|
||||
|
||||
def volup(self, delta=10):
|
||||
"""
|
||||
Turn up the volume
|
||||
|
||||
:param delta: Volume up delta (default: +10%)
|
||||
:type delta: int
|
||||
"""
|
||||
|
||||
volume = int(self.status().output['volume'])
|
||||
new_volume = volume+delta
|
||||
if new_volume <= 100:
|
||||
|
@ -75,6 +135,13 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
return self.status()
|
||||
|
||||
def voldown(self, delta=10):
|
||||
"""
|
||||
Turn down the volume
|
||||
|
||||
:param delta: Volume down delta (default: -10%)
|
||||
:type delta: int
|
||||
"""
|
||||
|
||||
volume = int(self.status().output['volume'])
|
||||
new_volume = volume-delta
|
||||
if new_volume >= 0:
|
||||
|
@ -82,40 +149,125 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
return self.status()
|
||||
|
||||
def random(self, value=None):
|
||||
"""
|
||||
Set shuffle mode
|
||||
|
||||
:param value: If set, set the random/shuffle state this value (true/false). Default: None (toggle current state)
|
||||
:type value: bool
|
||||
"""
|
||||
|
||||
if value is None:
|
||||
value = int(self.status().output['random'])
|
||||
value = 1 if value == 0 else 0
|
||||
return self._exec('random', value)
|
||||
|
||||
def repeat(self, value=None):
|
||||
"""
|
||||
Set repeat mode
|
||||
|
||||
:param value: If set, set the repeat state this value (true/false). Default: None (toggle current state)
|
||||
:type value: bool
|
||||
"""
|
||||
|
||||
if value is None:
|
||||
value = int(self.status().output['repeat'])
|
||||
value = 1 if value == 0 else 0
|
||||
return self._exec('repeat', value)
|
||||
|
||||
def add(self, resource):
|
||||
"""
|
||||
Add a resource (track, album, artist, folder etc.) to the current playlist
|
||||
|
||||
:param resource: Resource path or URI
|
||||
:type resource: str
|
||||
"""
|
||||
|
||||
return self._exec('add', resource)
|
||||
|
||||
def load(self, playlist):
|
||||
"""
|
||||
Load and play a playlist by name
|
||||
|
||||
:param playlist: Playlist name
|
||||
:type playlist: str
|
||||
"""
|
||||
|
||||
self._exec('load', playlist)
|
||||
return self.play()
|
||||
|
||||
def clear(self):
|
||||
""" Clear the current playlist """
|
||||
return self._exec('clear')
|
||||
|
||||
def seekcur(self, value):
|
||||
"""
|
||||
Seek to the specified position
|
||||
|
||||
:param value: Seek position in seconds, or delta string (e.g. '+15' or '-15') to indicate a seek relative to the current position
|
||||
:type value: int
|
||||
"""
|
||||
|
||||
return self._exec('seekcur', value)
|
||||
|
||||
def forward(self):
|
||||
""" Go forward by 15 seconds """
|
||||
|
||||
return self._exec('seekcur', '+15')
|
||||
|
||||
def back(self):
|
||||
""" Go backward by 15 seconds """
|
||||
|
||||
return self._exec('seekcur', '-15')
|
||||
|
||||
def status(self):
|
||||
"""
|
||||
:returns: The current state.
|
||||
|
||||
Example response::
|
||||
|
||||
output = {
|
||||
"volume": "9",
|
||||
"repeat": "0",
|
||||
"random": "0",
|
||||
"single": "0",
|
||||
"consume": "0",
|
||||
"playlist": "52",
|
||||
"playlistlength": "14",
|
||||
"xfade": "0",
|
||||
"state": "play",
|
||||
"song": "9",
|
||||
"songid": "3061",
|
||||
"nextsong": "10",
|
||||
"nextsongid": "3062",
|
||||
"time": "161:255",
|
||||
"elapsed": "161.967",
|
||||
"bitrate": "320"
|
||||
}
|
||||
"""
|
||||
|
||||
return Response(output=self.client.status())
|
||||
|
||||
def currentsong(self):
|
||||
"""
|
||||
:returns: The currently played track.
|
||||
|
||||
Example response::
|
||||
|
||||
output = {
|
||||
"file": "spotify:track:7CO5ADlDN3DcR2pwlnB14P",
|
||||
"time": "255",
|
||||
"artist": "Elbow",
|
||||
"album": "Little Fictions",
|
||||
"title": "Kindling",
|
||||
"date": "2017",
|
||||
"track": "10",
|
||||
"pos": "9",
|
||||
"id": "3061",
|
||||
"albumartist": "Elbow",
|
||||
"x-albumuri": "spotify:album:6q5KhDhf9BZkoob7uAnq19"
|
||||
}
|
||||
"""
|
||||
|
||||
track = self.client.currentsong()
|
||||
if 'title' in track and ('artist' not in track
|
||||
or not track['artist']
|
||||
|
@ -128,20 +280,96 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
return Response(output=track)
|
||||
|
||||
def playlistinfo(self):
|
||||
"""
|
||||
:returns: The tracks in the current playlist as a list of dicts.
|
||||
|
||||
Example output::
|
||||
|
||||
output = [
|
||||
{
|
||||
"file": "spotify:track:79VtgIoznishPUDWO7Kafu",
|
||||
"time": "355",
|
||||
"artist": "Elbow",
|
||||
"album": "Little Fictions",
|
||||
"title": "Trust the Sun",
|
||||
"date": "2017",
|
||||
"track": "3",
|
||||
"pos": "10",
|
||||
"id": "3062",
|
||||
"albumartist": "Elbow",
|
||||
"x-albumuri": "spotify:album:6q5KhDhf9BZkoob7uAnq19"
|
||||
},
|
||||
{
|
||||
"file": "spotify:track:3EzTre0pxmoMYRuhJKMHj6",
|
||||
"time": "219",
|
||||
"artist": "Elbow",
|
||||
"album": "Little Fictions",
|
||||
"title": "Gentle Storm",
|
||||
"date": "2017",
|
||||
"track": "2",
|
||||
"pos": "11",
|
||||
"id": "3063",
|
||||
"albumartist": "Elbow",
|
||||
"x-albumuri": "spotify:album:6q5KhDhf9BZkoob7uAnq19"
|
||||
},
|
||||
]
|
||||
"""
|
||||
|
||||
return Response(output=self.client.playlistinfo())
|
||||
|
||||
def listplaylists(self):
|
||||
"""
|
||||
:returns: The playlists available on the server as a list of dicts.
|
||||
|
||||
Example response::
|
||||
|
||||
output = [
|
||||
{
|
||||
"playlist": "Rock",
|
||||
"last-modified": "2018-06-25T21:28:19Z"
|
||||
},
|
||||
{
|
||||
"playlist": "Jazz",
|
||||
"last-modified": "2018-06-24T22:28:29Z"
|
||||
},
|
||||
{
|
||||
# ...
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
return Response(output=sorted(self.client.listplaylists(),
|
||||
key=lambda p: p['playlist']))
|
||||
|
||||
def lsinfo(self, uri=None):
|
||||
"""
|
||||
Returns the list of playlists and directories on the server
|
||||
"""
|
||||
|
||||
output = self.client.lsinfo(uri) if uri else self.client.lsinfo()
|
||||
return Response(output=output)
|
||||
|
||||
def plchanges(self, version):
|
||||
"""
|
||||
Show what has changed on the current playlist since a specified playlist
|
||||
version number.
|
||||
|
||||
:param version: Version number
|
||||
:type version: int
|
||||
|
||||
:returns: A list of dicts representing the songs being added since the specified version
|
||||
"""
|
||||
|
||||
return Response(output=self.client.plchanges(version))
|
||||
|
||||
def searchaddplaylist(self, name):
|
||||
"""
|
||||
Search and add a playlist by (partial or full) name
|
||||
|
||||
:param name: Playlist name, can be partial
|
||||
:type name: str
|
||||
"""
|
||||
|
||||
playlists = list(map(lambda _: _['playlist'],
|
||||
filter(lambda playlist:
|
||||
name.lower() in playlist['playlist'].lower(),
|
||||
|
@ -156,14 +384,38 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
return Response(output={})
|
||||
|
||||
def find(self, filter, *args, **kwargs):
|
||||
"""
|
||||
Find in the database/library by filter.
|
||||
|
||||
:param filter: Search filter. MPD treats it as a key-valued list (e.g. ``["artist", "Led Zeppelin", "album", "IV"]``)
|
||||
:type filter: list[str]
|
||||
:returns: list[dict]
|
||||
"""
|
||||
|
||||
return Response(
|
||||
output=self.client.find(*filter, *args, **kwargs))
|
||||
|
||||
def findadd(self, filter, *args, **kwargs):
|
||||
"""
|
||||
Find in the database/library by filter and add to the current playlist.
|
||||
|
||||
:param filter: Search filter. MPD treats it as a key-valued list (e.g. ``["artist", "Led Zeppelin", "album", "IV"]``)
|
||||
:type filter: list[str]
|
||||
:returns: list[dict]
|
||||
"""
|
||||
|
||||
return Response(
|
||||
output=self.client.findadd(*filter, *args, **kwargs))
|
||||
|
||||
def search(self, filter, *args, **kwargs):
|
||||
"""
|
||||
Free search by filter.
|
||||
|
||||
:param filter: Search filter. MPD treats it as a key-valued list (e.g. ``["artist", "Led Zeppelin", "album", "IV"]``)
|
||||
:type filter: list[str]
|
||||
:returns: list[dict]
|
||||
"""
|
||||
|
||||
items = self.client.search(*filter, *args, **kwargs)
|
||||
|
||||
# Spotify results first
|
||||
|
@ -173,6 +425,14 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
return Response(output=items)
|
||||
|
||||
def searchadd(self, filter, *args, **kwargs):
|
||||
"""
|
||||
Free search by filter and add the results to the current playlist.
|
||||
|
||||
:param filter: Search filter. MPD treats it as a key-valued list (e.g. ``["artist", "Led Zeppelin", "album", "IV"]``)
|
||||
:type filter: list[str]
|
||||
:returns: list[dict]
|
||||
"""
|
||||
|
||||
return Response(
|
||||
output=self.client.searchadd(*filter, *args, **kwargs))
|
||||
|
||||
|
|
Loading…
Reference in a new issue