forked from platypush/platypush
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.mail.rst
|
||||||
platypush/plugins/google.maps.rst
|
platypush/plugins/google.maps.rst
|
||||||
platypush/plugins/gpio.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):
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def get_measurement(self, *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')
|
raise NotImplementedError('get_measurement should be implemented in a derived class')
|
||||||
|
|
||||||
def get_data(self, *args, **kwargs):
|
def get_data(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Alias for ``get_measurement``
|
||||||
|
"""
|
||||||
|
|
||||||
return self.get_measurement(*args, **kwargs)
|
return self.get_measurement(*args, **kwargs)
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,14 +1,30 @@
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import RPi.GPIO as gpio
|
|
||||||
|
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
from platypush.plugins.gpio.sensor import GpioSensorPlugin
|
from platypush.plugins.gpio.sensor import GpioSensorPlugin
|
||||||
|
|
||||||
|
|
||||||
class GpioSensorDistancePlugin(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):
|
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)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.trigger_pin = trigger_pin
|
self.trigger_pin = trigger_pin
|
||||||
|
@ -24,6 +40,13 @@ class GpioSensorDistancePlugin(GpioSensorPlugin):
|
||||||
|
|
||||||
|
|
||||||
def get_measurement(self):
|
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.setmode(gpio.BCM)
|
||||||
gpio.setup(self.trigger_pin, gpio.OUT)
|
gpio.setup(self.trigger_pin, gpio.OUT)
|
||||||
gpio.setup(self.echo_pin, gpio.IN)
|
gpio.setup(self.echo_pin, gpio.IN)
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import enum
|
import enum
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import Adafruit_GPIO.SPI as SPI
|
|
||||||
import Adafruit_MCP3008
|
|
||||||
|
|
||||||
from platypush.plugins.gpio.sensor import GpioSensorPlugin
|
from platypush.plugins.gpio.sensor import GpioSensorPlugin
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
|
|
||||||
|
@ -15,15 +12,78 @@ class MCP3008Mode(enum.Enum):
|
||||||
|
|
||||||
class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
||||||
"""
|
"""
|
||||||
Plugin to read analog sensor values from an MCP3008 chipset,
|
Plugin to read analog sensor values from an MCP3008 chipset. The MCP3008
|
||||||
see https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/mcp3008
|
chipset is a circuit that allows you to read measuremnts from multiple
|
||||||
Requires adafruit-mcp3008 Python package
|
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
|
N_CHANNELS = 8
|
||||||
|
|
||||||
def __init__(self, CLK=None, MISO=None, MOSI=None, CS=None, spi_port=None,
|
def __init__(self, CLK=None, MISO=None, MOSI=None, CS=None, spi_port=None,
|
||||||
spi_device=None, channels=None, Vdd=3.3, *args, **kwargs):
|
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)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if CLK and MISO and MOSI and CS:
|
if CLK and MISO and MOSI and CS:
|
||||||
|
@ -47,6 +107,9 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
||||||
|
|
||||||
|
|
||||||
def _get_mcp(self):
|
def _get_mcp(self):
|
||||||
|
import Adafruit_GPIO.SPI as SPI
|
||||||
|
import Adafruit_MCP3008
|
||||||
|
|
||||||
if self.mode == MCP3008Mode.SOFTWARE:
|
if self.mode == MCP3008Mode.SOFTWARE:
|
||||||
self.mcp = Adafruit_MCP3008.MCP3008(clk=self.CLK, cs=self.CS,
|
self.mcp = Adafruit_MCP3008.MCP3008(clk=self.CLK, cs=self.CS,
|
||||||
miso=self.MISO, mosi=self.MOSI)
|
miso=self.MISO, mosi=self.MOSI)
|
||||||
|
@ -63,6 +126,27 @@ class GpioSensorMcp3008Plugin(GpioSensorPlugin):
|
||||||
|
|
||||||
|
|
||||||
def get_measurement(self):
|
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()
|
mcp = self._get_mcp()
|
||||||
values = {}
|
values = {}
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,65 @@ class Direction(enum.Enum):
|
||||||
|
|
||||||
|
|
||||||
class GpioZeroborgPlugin(Plugin):
|
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
|
_drive_thread = None
|
||||||
_can_run = False
|
_can_run = False
|
||||||
_direction = None
|
_direction = None
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, directions = {}, *args, **kwargs):
|
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
|
import platypush.plugins.gpio.zeroborg.lib as ZeroBorg
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -75,6 +128,16 @@ class GpioZeroborgPlugin(Plugin):
|
||||||
|
|
||||||
|
|
||||||
def drive(self, direction):
|
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
|
prev_direction = self._direction
|
||||||
|
|
||||||
self._can_run = True
|
self._can_run = True
|
||||||
|
@ -125,6 +188,10 @@ class GpioZeroborgPlugin(Plugin):
|
||||||
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Turns off the motors
|
||||||
|
"""
|
||||||
|
|
||||||
self._can_run = False
|
self._can_run = False
|
||||||
if self._drive_thread and threading.get_ident() != self._drive_thread.ident:
|
if self._drive_thread and threading.get_ident() != self._drive_thread.ident:
|
||||||
self._drive_thread.join()
|
self._drive_thread.join()
|
||||||
|
|
|
@ -5,7 +5,41 @@ from platypush.message.response import Response
|
||||||
from platypush.plugins import Plugin
|
from platypush.plugins import Plugin
|
||||||
|
|
||||||
class HttpRequestPlugin(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):
|
def _exec(self, method, url, output='text', **kwargs):
|
||||||
""" Available output types: text (default), json, binary """
|
""" Available output types: text (default), json, binary """
|
||||||
|
@ -21,26 +55,86 @@ class HttpRequestPlugin(Plugin):
|
||||||
|
|
||||||
|
|
||||||
def get(self, url, **kwargs):
|
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)
|
return self._exec(method='get', url=url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def post(self, 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)
|
return self._exec(method='post', url=url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def head(self, 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)
|
return self._exec(method='head', url=url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def put(self, 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)
|
return self._exec(method='put', url=url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def delete(self, 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)
|
return self._exec(method='delete', url=url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def options(self, 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)
|
return self._exec(method='options', url=url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,30 @@ from platypush.message.response import Response
|
||||||
from .. import Plugin
|
from .. import Plugin
|
||||||
|
|
||||||
class LastfmPlugin(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):
|
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_key = api_key
|
||||||
self.api_secret = api_secret
|
self.api_secret = api_secret
|
||||||
self.username = username
|
self.username = username
|
||||||
|
@ -20,6 +43,17 @@ class LastfmPlugin(Plugin):
|
||||||
|
|
||||||
|
|
||||||
def scrobble(self, artist, title, album=None, **kwargs):
|
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(
|
self.lastfm.scrobble(
|
||||||
artist = artist,
|
artist = artist,
|
||||||
title = title,
|
title = title,
|
||||||
|
@ -31,6 +65,17 @@ class LastfmPlugin(Plugin):
|
||||||
|
|
||||||
|
|
||||||
def update_now_playing(self, artist, title, album=None, **kwargs):
|
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(
|
self.lastfm.update_now_playing(
|
||||||
artist = artist,
|
artist = artist,
|
||||||
title = title,
|
title = title,
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
from .. import Plugin
|
from .. import Plugin
|
||||||
|
|
||||||
class LightPlugin(Plugin):
|
class LightPlugin(Plugin):
|
||||||
|
"""
|
||||||
|
Abstract plugin to interface your logic with lights/bulbs.
|
||||||
|
"""
|
||||||
|
|
||||||
def on(self):
|
def on(self):
|
||||||
|
""" Turn the light on """
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def off(self):
|
def off(self):
|
||||||
|
""" Turn the light off """
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def toggle(self):
|
def toggle(self):
|
||||||
|
""" Toggle the light status (on/off) """
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
|
""" Get the light status """
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,13 @@ from platypush.message.response import Response
|
||||||
from .. import LightPlugin
|
from .. import LightPlugin
|
||||||
|
|
||||||
class LightHuePlugin(LightPlugin):
|
class LightHuePlugin(LightPlugin):
|
||||||
""" Philips Hue lights plugin """
|
"""
|
||||||
|
Philips Hue lights plugin.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **phue** (``pip install phue``)
|
||||||
|
"""
|
||||||
|
|
||||||
MAX_BRI = 255
|
MAX_BRI = 255
|
||||||
MAX_SAT = 255
|
MAX_SAT = 255
|
||||||
|
@ -31,11 +37,14 @@ class LightHuePlugin(LightPlugin):
|
||||||
|
|
||||||
def __init__(self, bridge, lights=None, groups=None):
|
def __init__(self, bridge, lights=None, groups=None):
|
||||||
"""
|
"""
|
||||||
Constructor
|
:param bridge: Bridge address or hostname
|
||||||
Params:
|
:type bridge: str
|
||||||
bridge -- Bridge address or hostname
|
|
||||||
lights -- Lights to be controlled (default: all)
|
:param lights: Default lights to be controlled (default: all)
|
||||||
groups -- Groups to be controlled (default: all)
|
:type lights: list[str]
|
||||||
|
|
||||||
|
:param groups Default groups to be controlled (default: all)
|
||||||
|
:type groups: list[str]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -66,6 +75,14 @@ class LightHuePlugin(LightPlugin):
|
||||||
self.lights.extend([l.name for l in g.lights])
|
self.lights.extend([l.name for l in g.lights])
|
||||||
|
|
||||||
def connect(self):
|
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
|
# Lazy init
|
||||||
if not self.bridge:
|
if not self.bridge:
|
||||||
self.bridge = Bridge(self.bridge_address)
|
self.bridge = Bridge(self.bridge_address)
|
||||||
|
@ -83,14 +100,127 @@ class LightHuePlugin(LightPlugin):
|
||||||
|
|
||||||
|
|
||||||
def get_scenes(self):
|
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())
|
return Response(output=self.bridge.get_scene())
|
||||||
|
|
||||||
|
|
||||||
def get_lights(self):
|
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())
|
return Response(output=self.bridge.get_light())
|
||||||
|
|
||||||
|
|
||||||
def get_groups(self):
|
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())
|
return Response(output=self.bridge.get_group())
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,40 +259,132 @@ class LightHuePlugin(LightPlugin):
|
||||||
return Response(output='ok')
|
return Response(output='ok')
|
||||||
|
|
||||||
def set_light(self, light, **kwargs):
|
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.connect()
|
||||||
self.bridge.set_light(light, **kwargs)
|
self.bridge.set_light(light, **kwargs)
|
||||||
return Response(output='ok')
|
return Response(output='ok')
|
||||||
|
|
||||||
def set_group(self, group, **kwargs):
|
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.connect()
|
||||||
self.bridge.set_group(group, **kwargs)
|
self.bridge.set_group(group, **kwargs)
|
||||||
return Response(output='ok')
|
return Response(output='ok')
|
||||||
|
|
||||||
def on(self, lights=[], groups=[]):
|
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)
|
return self._exec('on', True, lights=lights, groups=groups)
|
||||||
|
|
||||||
def off(self, lights=[], 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)
|
return self._exec('on', False, lights=lights, groups=groups)
|
||||||
|
|
||||||
def bri(self, value, lights=[], 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),
|
return self._exec('bri', int(value) % (self.MAX_BRI+1),
|
||||||
lights=lights, groups=groups)
|
lights=lights, groups=groups)
|
||||||
|
|
||||||
def sat(self, value, lights=[], 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),
|
return self._exec('sat', int(value) % (self.MAX_SAT+1),
|
||||||
lights=lights, groups=groups)
|
lights=lights, groups=groups)
|
||||||
|
|
||||||
def hue(self, value, lights=[], 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),
|
return self._exec('hue', int(value) % (self.MAX_HUE+1),
|
||||||
lights=lights, groups=groups)
|
lights=lights, groups=groups)
|
||||||
|
|
||||||
def scene(self, name, lights=[], 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)
|
return self._exec('scene', name=name, lights=lights, groups=groups)
|
||||||
|
|
||||||
def is_animation_running(self):
|
def is_animation_running(self):
|
||||||
|
"""
|
||||||
|
:returns: True if there is an animation running, false otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
return self.animation_thread is not None
|
return self.animation_thread is not None
|
||||||
|
|
||||||
def stop_animation(self):
|
def stop_animation(self):
|
||||||
|
"""
|
||||||
|
Stop a running animation if any
|
||||||
|
"""
|
||||||
|
|
||||||
if self.animation_thread and self.animation_thread.is_alive():
|
if self.animation_thread and self.animation_thread.is_alive():
|
||||||
self.redis.rpush(self.ANIMATION_CTRL_QUEUE_NAME, 'STOP')
|
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],
|
hue_range=[0, MAX_HUE], sat_range=[0, MAX_SAT],
|
||||||
bri_range=[MAX_BRI-1, MAX_BRI], lights=None, groups=None,
|
bri_range=[MAX_BRI-1, MAX_BRI], lights=None, groups=None,
|
||||||
hue_step=1000, sat_step=2, bri_step=1, transition_seconds=1.0):
|
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):
|
def _initialize_light_attrs(lights):
|
||||||
if animation == self.Animation.COLOR_TRANSITION:
|
if animation == self.Animation.COLOR_TRANSITION:
|
||||||
|
|
|
@ -7,14 +7,23 @@ from platypush.plugins import Plugin
|
||||||
|
|
||||||
class MidiPlugin(Plugin):
|
class MidiPlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
Virtual MIDI controller plugin.
|
Virtual MIDI controller plugin. It allows you to send custom MIDI messages
|
||||||
It requires python-rtmidi - https://pypi.org/project/python-rtmidi/
|
to any connected devices.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **python-rtmidi** (``pip install python-rtmidi``)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_played_notes = set()
|
_played_notes = set()
|
||||||
|
|
||||||
def __init__(self, device_name='Platypush virtual MIDI output',
|
def __init__(self, device_name='Platypush virtual MIDI output',
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
|
"""
|
||||||
|
:param device_name: MIDI virtual device name (default: *Platypush virtual MIDI output*)
|
||||||
|
:type device_name: str
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.device_name = device_name
|
self.device_name = device_name
|
||||||
|
@ -32,30 +41,32 @@ class MidiPlugin(Plugin):
|
||||||
|
|
||||||
def send_message(self, values, *args, **kwargs):
|
def send_message(self, values, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Values is expected to be a list containing the MIDI command code and
|
: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
|
||||||
the command parameters - see reference at
|
:type values: list[int]
|
||||||
https://ccrma.stanford.edu/~craig/articles/linuxmidi/misc/essenmidi.html
|
|
||||||
|
|
||||||
Available MIDI commands:
|
Available MIDI commands:
|
||||||
0x80 Note Off
|
* ``0x80`` Note Off
|
||||||
0x90 Note On
|
* ``0x90`` Note On
|
||||||
0xA0 Aftertouch
|
* ``0xA0`` Aftertouch
|
||||||
0xB0 Continuous controller
|
* ``0xB0`` Continuous controller
|
||||||
0xC0 Patch change
|
* ``0xC0`` Patch change
|
||||||
0xD0 Channel Pressure
|
* ``0xD0`` Channel Pressure
|
||||||
0xE0 Pitch bend
|
* ``0xE0`` Pitch bend
|
||||||
0xF0 Start of system exclusive message
|
* ``0xF0`` Start of system exclusive message
|
||||||
0xF1 MIDI Time Code Quarter Frame (Sys Common)
|
* ``0xF1`` MIDI Time Code Quarter Frame (Sys Common)
|
||||||
0xF2 Song Position Pointer (Sys Common)
|
* ``0xF2`` Song Position Pointer (Sys Common)
|
||||||
0xF3 Song Select
|
* ``0xF3`` Song Select
|
||||||
0xF6 Tune Request (Sys Common)
|
* ``0xF6`` Tune Request (Sys Common)
|
||||||
0xF7 End of system exclusive message
|
* ``0xF7`` End of system exclusive message
|
||||||
0xF8 Timing Clock (Sys Realtime)
|
* ``0xF8`` Timing Clock (Sys Realtime)
|
||||||
0xFA Start (Sys Realtime)
|
* ``0xFA`` Start (Sys Realtime)
|
||||||
0xFB Continue (Sys Realtime)
|
* ``0xFB`` Continue (Sys Realtime)
|
||||||
0xFC Stop (Sys Realtime)
|
* ``0xFC`` Stop (Sys Realtime)
|
||||||
0xFE Active Sensing (Sys Realtime)
|
* ``0xFE`` Active Sensing (Sys Realtime)
|
||||||
0xFF System Reset (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)
|
self.midiout.send_message(values, *args, **kwargs)
|
||||||
|
@ -64,11 +75,16 @@ class MidiPlugin(Plugin):
|
||||||
|
|
||||||
def play_note(self, note, velocity, duration=0):
|
def play_note(self, note, velocity, duration=0):
|
||||||
"""
|
"""
|
||||||
Params:
|
Play a note with selected velocity and duration.
|
||||||
- note: MIDI note in range 0-127 with #60 = C4
|
|
||||||
- velocity: MIDI note velocity in range 0-127
|
:param note: MIDI note in range 0-127 with #60 = C4
|
||||||
- duration: Note duration in (float) seconds. Pass 0 if you don't
|
:type note: int
|
||||||
want the note to get off
|
|
||||||
|
: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
|
self.send_message([0x90, note, velocity]) # Note on
|
||||||
|
@ -81,14 +97,26 @@ class MidiPlugin(Plugin):
|
||||||
|
|
||||||
|
|
||||||
def release_note(self, note):
|
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.send_message([0x80, note, 0]) # Note off
|
||||||
self._played_notes.remove(note)
|
self._played_notes.remove(note)
|
||||||
|
|
||||||
|
|
||||||
def release_all_notes(self):
|
def release_all_notes(self):
|
||||||
|
"""
|
||||||
|
Release all the notes being played.
|
||||||
|
"""
|
||||||
|
|
||||||
played_notes = self._played_notes.copy()
|
played_notes = self._played_notes.copy()
|
||||||
for note in played_notes:
|
for note in played_notes:
|
||||||
self.release_note(note)
|
self.release_note(note)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,27 @@ from platypush.plugins import Plugin
|
||||||
|
|
||||||
|
|
||||||
class MqttPlugin(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):
|
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)
|
try: msg = json.dumps(msg)
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,27 @@ from platypush.message.response import Response
|
||||||
from .. import MusicPlugin
|
from .. import MusicPlugin
|
||||||
|
|
||||||
class MusicMpdPlugin(MusicPlugin):
|
class MusicMpdPlugin(MusicPlugin):
|
||||||
def __init__(self, host, port):
|
|
||||||
"""
|
"""
|
||||||
Constructor
|
This plugin allows you to interact with an MPD/Mopidy music server. MPD
|
||||||
Params:
|
(https://www.musicpd.org/) is a flexible server-side protocol/application
|
||||||
host -- MPD host
|
for handling music collections and playing music, mostly aimed to manage
|
||||||
port -- MPD port
|
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):
|
||||||
|
"""
|
||||||
|
:param host: MPD IP/hostname
|
||||||
|
:type host: str
|
||||||
|
|
||||||
|
:param port: MPD port (default: 6600)
|
||||||
|
:type port: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.host = host
|
self.host = host
|
||||||
|
@ -24,50 +39,95 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
def play(self, resource=None):
|
def play(self, resource=None):
|
||||||
|
"""
|
||||||
|
Play a resource by path/URI
|
||||||
|
|
||||||
|
:param resource: Resource path/URI
|
||||||
|
:type resource: str
|
||||||
|
"""
|
||||||
|
|
||||||
if resource:
|
if resource:
|
||||||
self.clear()
|
self.clear()
|
||||||
self.add(resource)
|
self.add(resource)
|
||||||
return self._exec('play')
|
return self._exec('play')
|
||||||
|
|
||||||
def play_pos(self, pos):
|
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)
|
return self._exec('play', pos)
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
|
""" Pause playback """
|
||||||
|
|
||||||
status = self.status().output['state']
|
status = self.status().output['state']
|
||||||
if status == 'play': return self._exec('pause')
|
if status == 'play': return self._exec('pause')
|
||||||
else: return self._exec('play')
|
else: return self._exec('play')
|
||||||
|
|
||||||
def pause_if_playing(self):
|
def pause_if_playing(self):
|
||||||
|
""" Pause playback only if it's playing """
|
||||||
|
|
||||||
status = self.status().output['state']
|
status = self.status().output['state']
|
||||||
if status == 'play': return self._exec('pause')
|
if status == 'play': return self._exec('pause')
|
||||||
else: return Response(output={})
|
else: return Response(output={})
|
||||||
|
|
||||||
def play_if_paused(self):
|
def play_if_paused(self):
|
||||||
|
""" Play only if it's paused (resume) """
|
||||||
|
|
||||||
status = self.status().output['state']
|
status = self.status().output['state']
|
||||||
if status == 'pause': return self._exec('play')
|
if status == 'pause': return self._exec('play')
|
||||||
else: return Response(output={})
|
else: return Response(output={})
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
""" Stop playback """
|
||||||
|
|
||||||
return self._exec('stop')
|
return self._exec('stop')
|
||||||
|
|
||||||
def play_or_stop(self):
|
def play_or_stop(self):
|
||||||
|
""" Play or stop (play state toggle) """
|
||||||
status = self.status().output['state']
|
status = self.status().output['state']
|
||||||
if status == 'play': return self._exec('stop')
|
if status == 'play': return self._exec('stop')
|
||||||
else: return self._exec('play')
|
else: return self._exec('play')
|
||||||
|
|
||||||
def playid(self, track_id):
|
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)
|
return self._exec('playid', track_id)
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
|
""" Play the next track """
|
||||||
return self._exec('next')
|
return self._exec('next')
|
||||||
|
|
||||||
def previous(self):
|
def previous(self):
|
||||||
|
""" Play the previous track """
|
||||||
return self._exec('previous')
|
return self._exec('previous')
|
||||||
|
|
||||||
def setvol(self, vol):
|
def setvol(self, vol):
|
||||||
|
"""
|
||||||
|
Set the volume
|
||||||
|
|
||||||
|
:param vol: Volume value (range: 0-100)
|
||||||
|
:type vol: int
|
||||||
|
"""
|
||||||
return self._exec('setvol', vol)
|
return self._exec('setvol', vol)
|
||||||
|
|
||||||
def volup(self, delta=10):
|
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'])
|
volume = int(self.status().output['volume'])
|
||||||
new_volume = volume+delta
|
new_volume = volume+delta
|
||||||
if new_volume <= 100:
|
if new_volume <= 100:
|
||||||
|
@ -75,6 +135,13 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
def voldown(self, delta=10):
|
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'])
|
volume = int(self.status().output['volume'])
|
||||||
new_volume = volume-delta
|
new_volume = volume-delta
|
||||||
if new_volume >= 0:
|
if new_volume >= 0:
|
||||||
|
@ -82,40 +149,125 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
def random(self, value=None):
|
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:
|
if value is None:
|
||||||
value = int(self.status().output['random'])
|
value = int(self.status().output['random'])
|
||||||
value = 1 if value == 0 else 0
|
value = 1 if value == 0 else 0
|
||||||
return self._exec('random', value)
|
return self._exec('random', value)
|
||||||
|
|
||||||
def repeat(self, value=None):
|
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:
|
if value is None:
|
||||||
value = int(self.status().output['repeat'])
|
value = int(self.status().output['repeat'])
|
||||||
value = 1 if value == 0 else 0
|
value = 1 if value == 0 else 0
|
||||||
return self._exec('repeat', value)
|
return self._exec('repeat', value)
|
||||||
|
|
||||||
def add(self, resource):
|
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)
|
return self._exec('add', resource)
|
||||||
|
|
||||||
def load(self, playlist):
|
def load(self, playlist):
|
||||||
|
"""
|
||||||
|
Load and play a playlist by name
|
||||||
|
|
||||||
|
:param playlist: Playlist name
|
||||||
|
:type playlist: str
|
||||||
|
"""
|
||||||
|
|
||||||
self._exec('load', playlist)
|
self._exec('load', playlist)
|
||||||
return self.play()
|
return self.play()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
""" Clear the current playlist """
|
||||||
return self._exec('clear')
|
return self._exec('clear')
|
||||||
|
|
||||||
def seekcur(self, value):
|
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)
|
return self._exec('seekcur', value)
|
||||||
|
|
||||||
def forward(self):
|
def forward(self):
|
||||||
|
""" Go forward by 15 seconds """
|
||||||
|
|
||||||
return self._exec('seekcur', '+15')
|
return self._exec('seekcur', '+15')
|
||||||
|
|
||||||
def back(self):
|
def back(self):
|
||||||
|
""" Go backward by 15 seconds """
|
||||||
|
|
||||||
return self._exec('seekcur', '-15')
|
return self._exec('seekcur', '-15')
|
||||||
|
|
||||||
def status(self):
|
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())
|
return Response(output=self.client.status())
|
||||||
|
|
||||||
def currentsong(self):
|
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()
|
track = self.client.currentsong()
|
||||||
if 'title' in track and ('artist' not in track
|
if 'title' in track and ('artist' not in track
|
||||||
or not track['artist']
|
or not track['artist']
|
||||||
|
@ -128,20 +280,96 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return Response(output=track)
|
return Response(output=track)
|
||||||
|
|
||||||
def playlistinfo(self):
|
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())
|
return Response(output=self.client.playlistinfo())
|
||||||
|
|
||||||
def listplaylists(self):
|
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(),
|
return Response(output=sorted(self.client.listplaylists(),
|
||||||
key=lambda p: p['playlist']))
|
key=lambda p: p['playlist']))
|
||||||
|
|
||||||
def lsinfo(self, uri=None):
|
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()
|
output = self.client.lsinfo(uri) if uri else self.client.lsinfo()
|
||||||
return Response(output=output)
|
return Response(output=output)
|
||||||
|
|
||||||
def plchanges(self, version):
|
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))
|
return Response(output=self.client.plchanges(version))
|
||||||
|
|
||||||
def searchaddplaylist(self, name):
|
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'],
|
playlists = list(map(lambda _: _['playlist'],
|
||||||
filter(lambda playlist:
|
filter(lambda playlist:
|
||||||
name.lower() in playlist['playlist'].lower(),
|
name.lower() in playlist['playlist'].lower(),
|
||||||
|
@ -156,14 +384,38 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return Response(output={})
|
return Response(output={})
|
||||||
|
|
||||||
def find(self, filter, *args, **kwargs):
|
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(
|
return Response(
|
||||||
output=self.client.find(*filter, *args, **kwargs))
|
output=self.client.find(*filter, *args, **kwargs))
|
||||||
|
|
||||||
def findadd(self, 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(
|
return Response(
|
||||||
output=self.client.findadd(*filter, *args, **kwargs))
|
output=self.client.findadd(*filter, *args, **kwargs))
|
||||||
|
|
||||||
def search(self, 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)
|
items = self.client.search(*filter, *args, **kwargs)
|
||||||
|
|
||||||
# Spotify results first
|
# Spotify results first
|
||||||
|
@ -173,6 +425,14 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return Response(output=items)
|
return Response(output=items)
|
||||||
|
|
||||||
def searchadd(self, filter, *args, **kwargs):
|
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(
|
return Response(
|
||||||
output=self.client.searchadd(*filter, *args, **kwargs))
|
output=self.client.searchadd(*filter, *args, **kwargs))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue