Added zigbee2mqtt integration (see #76)
This commit is contained in:
parent
976c4c4854
commit
8255f9af28
15 changed files with 1049 additions and 161 deletions
|
@ -7,6 +7,7 @@ Backends
|
||||||
:caption: Backends:
|
:caption: Backends:
|
||||||
|
|
||||||
platypush/backend/adafruit.io.rst
|
platypush/backend/adafruit.io.rst
|
||||||
|
platypush/backend/assistant.rst
|
||||||
platypush/backend/assistant.google.rst
|
platypush/backend/assistant.google.rst
|
||||||
platypush/backend/assistant.snowboy.rst
|
platypush/backend/assistant.snowboy.rst
|
||||||
platypush/backend/bluetooth.rst
|
platypush/backend/bluetooth.rst
|
||||||
|
@ -59,3 +60,4 @@ Backends
|
||||||
platypush/backend/weather.darksky.rst
|
platypush/backend/weather.darksky.rst
|
||||||
platypush/backend/websocket.rst
|
platypush/backend/websocket.rst
|
||||||
platypush/backend/wiimote.rst
|
platypush/backend/wiimote.rst
|
||||||
|
platypush/backend/zigbee.mqtt.rst
|
||||||
|
|
|
@ -54,3 +54,4 @@ Events
|
||||||
platypush/events/web.widget.rst
|
platypush/events/web.widget.rst
|
||||||
platypush/events/wiimote.rst
|
platypush/events/wiimote.rst
|
||||||
platypush/events/zeroborg.rst
|
platypush/events/zeroborg.rst
|
||||||
|
platypush/events/zigbee.mqtt.rst
|
||||||
|
|
5
docs/source/platypush/backend/assistant.rst
Normal file
5
docs/source/platypush/backend/assistant.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.assistant``
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.assistant
|
||||||
|
:members:
|
5
docs/source/platypush/backend/zigbee.mqtt.rst
Normal file
5
docs/source/platypush/backend/zigbee.mqtt.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.zigbee.mqtt``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.zigbee.mqtt
|
||||||
|
:members:
|
5
docs/source/platypush/events/zigbee.mqtt.rst
Normal file
5
docs/source/platypush/events/zigbee.mqtt.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.message.event.zigbee.mqtt``
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.message.event.zigbee.mqtt
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/zigbee.mqtt.rst
Normal file
5
docs/source/platypush/plugins/zigbee.mqtt.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.zigbee.mqtt``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.zigbee.mqtt
|
||||||
|
:members:
|
|
@ -110,3 +110,4 @@ Plugins
|
||||||
platypush/plugins/weather.darksky.rst
|
platypush/plugins/weather.darksky.rst
|
||||||
platypush/plugins/websocket.rst
|
platypush/plugins/websocket.rst
|
||||||
platypush/plugins/wiimote.rst
|
platypush/plugins/wiimote.rst
|
||||||
|
platypush/plugins/zigbee.mqtt.rst
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from platypush.backend import Backend
|
from platypush.backend import Backend
|
||||||
from platypush.context import get_plugin
|
from platypush.context import get_plugin
|
||||||
|
@ -27,53 +28,50 @@ class MqttBackend(Backend):
|
||||||
|
|
||||||
_default_mqtt_port = 1883
|
_default_mqtt_port = 1883
|
||||||
|
|
||||||
def __init__(self, host, port=_default_mqtt_port, topic='platypush_bus_mq',
|
def __init__(self, host: Optional[str] = None, port: int = _default_mqtt_port,
|
||||||
tls_cafile=None, tls_certfile=None, tls_keyfile=None,
|
topic='platypush_bus_mq', subscribe_default_topic: bool = True,
|
||||||
tls_version=None, tls_ciphers=None, username=None,
|
tls_cafile: Optional[str] = None, tls_certfile: Optional[str] = None,
|
||||||
password=None, listeners=None, *args, **kwargs):
|
tls_keyfile: Optional[str] = None, tls_version: Optional[str] = None,
|
||||||
|
tls_ciphers: Optional[str] = None, username: Optional[str] = None,
|
||||||
|
password: Optional[str] = None, listeners=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param host: MQTT broker host
|
:param host: MQTT broker host
|
||||||
:type host: str
|
|
||||||
|
|
||||||
:param port: MQTT broker port (default: 1883)
|
:param port: MQTT broker port (default: 1883)
|
||||||
:type port: int
|
|
||||||
|
|
||||||
:param topic: Topic to read messages from (default: ``platypush_bus_mq/<device_id>``)
|
:param topic: Topic to read messages from (default: ``platypush_bus_mq/<device_id>``)
|
||||||
:type topic: str
|
:param subscribe_default_topic: Whether the backend should subscribe the default topic (default:
|
||||||
|
``platypush_bus_mq/<device_id>``) and execute the messages received there as action requests
|
||||||
|
(default: True).
|
||||||
:param tls_cafile: If TLS/SSL is enabled on the MQTT server and the certificate requires a certificate authority
|
:param tls_cafile: If TLS/SSL is enabled on the MQTT server and the certificate requires a certificate authority
|
||||||
to authenticate it, `ssl_cafile` will point to the provided ca.crt file (default: None)
|
to authenticate it, `ssl_cafile` will point to the provided ca.crt file (default: None)
|
||||||
:type tls_cafile: str
|
|
||||||
|
|
||||||
:param tls_certfile: If TLS/SSL is enabled on the MQTT server and a client certificate it required, specify it
|
:param tls_certfile: If TLS/SSL is enabled on the MQTT server and a client certificate it required, specify it
|
||||||
here (default: None)
|
here (default: None)
|
||||||
:type tls_certfile: str
|
|
||||||
|
|
||||||
:param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a client certificate key it required,
|
:param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a client certificate key it required,
|
||||||
specify it here (default: None) :type tls_keyfile: str
|
specify it here (default: None) :type tls_keyfile: str
|
||||||
|
|
||||||
:param tls_version: If TLS/SSL is enabled on the MQTT server and it requires a certain TLS version, specify it
|
:param tls_version: If TLS/SSL is enabled on the MQTT server and it requires a certain TLS version, specify it
|
||||||
here (default: None)
|
here (default: None)
|
||||||
:type tls_version: str
|
|
||||||
|
|
||||||
:param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an explicit list of supported ciphers is
|
:param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an explicit list of supported ciphers is
|
||||||
required, specify it here (default: None)
|
required, specify it here (default: None)
|
||||||
:type tls_ciphers: str
|
|
||||||
|
|
||||||
:param username: Specify it if the MQTT server requires authentication (default: None)
|
:param username: Specify it if the MQTT server requires authentication (default: None)
|
||||||
:type username: str
|
|
||||||
|
|
||||||
:param password: Specify it if the MQTT server requires authentication (default: None)
|
:param password: Specify it if the MQTT server requires authentication (default: None)
|
||||||
:type password: str
|
|
||||||
|
|
||||||
:param listeners: If specified then the MQTT backend will also listen for
|
:param listeners: If specified then the MQTT backend will also listen for
|
||||||
messages on the additional configured message queues. This parameter
|
messages on the additional configured message queues. This parameter
|
||||||
is a list of maps where each item supports the same arguments passed
|
is a list of maps where each item supports the same arguments passed
|
||||||
to the main backend configuration (host, port, topic, password etc.).
|
to the main backend configuration (host, port, topic, password etc.).
|
||||||
Note that the message queue configured on the main configuration
|
Note that the message queue configured on the main configuration
|
||||||
will expect valid Platypush messages that then can execute, while
|
will expect valid Platypush messages that then can execute, while
|
||||||
message queues registered to the listeners will accept any message.
|
message queues registered to the listeners will accept any message. Example::
|
||||||
:type listeners: list[dict]
|
|
||||||
|
listeners:
|
||||||
|
- host: localhost
|
||||||
|
topics:
|
||||||
|
- topic1
|
||||||
|
- topic2
|
||||||
|
- topic3
|
||||||
|
- host: sensors
|
||||||
|
topics:
|
||||||
|
- topic4
|
||||||
|
- topic5
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -81,6 +79,7 @@ class MqttBackend(Backend):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.topic = '{}/{}'.format(topic, self.device_id)
|
self.topic = '{}/{}'.format(topic, self.device_id)
|
||||||
|
self.subscribe_default_topic = subscribe_default_topic
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
self._client = None
|
self._client = None
|
||||||
|
@ -99,38 +98,30 @@ class MqttBackend(Backend):
|
||||||
self.tls_ciphers = tls_ciphers
|
self.tls_ciphers = tls_ciphers
|
||||||
self.listeners_conf = listeners or []
|
self.listeners_conf = listeners or []
|
||||||
|
|
||||||
def send_message(self, msg, **kwargs):
|
def send_message(self, msg, topic: Optional[str] = None, **kwargs):
|
||||||
try:
|
try:
|
||||||
client = get_plugin('mqtt')
|
client = get_plugin('mqtt')
|
||||||
client.send_message(topic=self.topic, msg=msg, host=self.host,
|
client.send_message(topic=topic or self.topic, msg=msg, host=self.host,
|
||||||
port=self.port, username=self.username,
|
port=self.port, username=self.username,
|
||||||
password=self.password, tls_cafile=self.tls_cafile,
|
password=self.password, tls_cafile=self.tls_cafile,
|
||||||
tls_certfile=self.tls_certfile,
|
tls_certfile=self.tls_certfile,
|
||||||
tls_keyfile=self.tls_keyfile,
|
tls_keyfile=self.tls_keyfile,
|
||||||
tls_version=self.tls_version,
|
tls_version=self.tls_version,
|
||||||
tls_ciphers=self.tls_ciphers)
|
tls_ciphers=self.tls_ciphers, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
self.logger.exception(e)
|
||||||
|
|
||||||
def _initialize_listeners(self, listeners_conf):
|
@staticmethod
|
||||||
import paho.mqtt.client as mqtt
|
def on_connect(*topics):
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
# noinspection PyShadowingNames
|
|
||||||
def listener_thread(client, host, port):
|
|
||||||
client.connect(host, port, 60)
|
|
||||||
client.loop_forever()
|
|
||||||
|
|
||||||
# noinspection PyShadowingNames
|
|
||||||
def on_connect(topics):
|
|
||||||
# noinspection PyShadowingNames,PyUnusedLocal
|
|
||||||
def handler(client, userdata, flags, rc):
|
def handler(client, userdata, flags, rc):
|
||||||
for topic in topics:
|
for topic in topics:
|
||||||
client.subscribe(topic)
|
client.subscribe(topic)
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
# noinspection PyShadowingNames,PyUnusedLocal
|
def on_mqtt_message(self):
|
||||||
def on_message(client, userdata, msg):
|
def handler(client, _, msg):
|
||||||
data = msg.payload
|
data = msg.payload
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
try:
|
||||||
|
@ -140,9 +131,19 @@ class MqttBackend(Backend):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
self.bus.post(MQTTMessageEvent(host=client._host, port=client._port,
|
self.bus.post(MQTTMessageEvent(host=client._host, port=client._port, topic=msg.topic, msg=data))
|
||||||
topic=msg.topic, msg=data))
|
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def _initialize_listeners(self, listeners_conf):
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def listener_thread(client, host, port):
|
||||||
|
client.connect(host, port)
|
||||||
|
client.loop_forever()
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames,PyUnusedLocal
|
||||||
for i, listener in enumerate(listeners_conf):
|
for i, listener in enumerate(listeners_conf):
|
||||||
host = listener.get('host')
|
host = listener.get('host')
|
||||||
port = listener.get('port', self._default_mqtt_port)
|
port = listener.get('port', self._default_mqtt_port)
|
||||||
|
@ -157,8 +158,8 @@ class MqttBackend(Backend):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
client = mqtt.Client()
|
client = mqtt.Client()
|
||||||
client.on_connect = on_connect(topics)
|
client.on_connect = self.on_connect(*topics)
|
||||||
client.on_message = on_message
|
client.on_message = self.on_mqtt_message()
|
||||||
|
|
||||||
if username and password:
|
if username and password:
|
||||||
client.username_pw_set(username, password)
|
client.username_pw_set(username, password)
|
||||||
|
@ -173,13 +174,8 @@ class MqttBackend(Backend):
|
||||||
threading.Thread(target=listener_thread, kwargs={
|
threading.Thread(target=listener_thread, kwargs={
|
||||||
'client': client, 'host': host, 'port': port}).start()
|
'client': client, 'host': host, 'port': port}).start()
|
||||||
|
|
||||||
def run(self):
|
def on_exec_message(self):
|
||||||
# noinspection PyUnusedLocal
|
def handler(_, __, msg):
|
||||||
def on_connect(client, userdata, flags, rc):
|
|
||||||
client.subscribe(self.topic)
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
|
||||||
def on_message(client, userdata, msg):
|
|
||||||
# noinspection PyShadowingNames
|
# noinspection PyShadowingNames
|
||||||
def response_thread(msg):
|
def response_thread(msg):
|
||||||
set_thread_name('MQTTProcessor')
|
set_thread_name('MQTTProcessor')
|
||||||
|
@ -196,7 +192,8 @@ class MqttBackend(Backend):
|
||||||
msg = msg.payload.decode('utf-8')
|
msg = msg.payload.decode('utf-8')
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
try:
|
||||||
msg = Message.build(json.loads(msg))
|
msg = json.loads(msg)
|
||||||
|
msg = Message.build(msg)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -212,17 +209,22 @@ class MqttBackend(Backend):
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(msg, Request):
|
if isinstance(msg, Request):
|
||||||
threading.Thread(target=response_thread,
|
threading.Thread(target=response_thread, name='MQTTProcessor', args=(msg,)).start()
|
||||||
name='MQTTProcessor',
|
|
||||||
args=(msg,)).start()
|
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def run(self):
|
||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
super().run()
|
super().run()
|
||||||
self._client = mqtt.Client()
|
self._client = None
|
||||||
self._client.on_connect = on_connect
|
|
||||||
self._client.on_message = on_message
|
|
||||||
|
|
||||||
|
if self.host:
|
||||||
|
self._client = mqtt.Client()
|
||||||
|
if self.subscribe_default_topic:
|
||||||
|
self._client.on_connect = self.on_connect(self.topic)
|
||||||
|
|
||||||
|
self._client.on_message = self.on_exec_message()
|
||||||
if self.username and self.password:
|
if self.username and self.password:
|
||||||
self._client.username_pw_set(self.username, self.password)
|
self._client.username_pw_set(self.username, self.password)
|
||||||
|
|
||||||
|
@ -236,6 +238,7 @@ class MqttBackend(Backend):
|
||||||
format(self.host, self.port, self.topic))
|
format(self.host, self.port, self.topic))
|
||||||
|
|
||||||
self._initialize_listeners(self.listeners_conf)
|
self._initialize_listeners(self.listeners_conf)
|
||||||
|
if self._client:
|
||||||
self._client.loop_forever()
|
self._client.loop_forever()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
@ -250,9 +253,9 @@ class MqttBackend(Backend):
|
||||||
listener.loop_stop()
|
listener.loop_stop()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
self.logger.warning('Could not stop listener ' +
|
self.logger.warning('Could not stop listener {host}:{port}: {error}'.format(
|
||||||
'{host}:{port}: {error}'.format(
|
|
||||||
host=listener._host, port=listener._port,
|
host=listener._host, port=listener._port,
|
||||||
error=str(e)))
|
error=str(e)))
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
0
platypush/backend/zigbee/__init__.py
Normal file
0
platypush/backend/zigbee/__init__.py
Normal file
212
platypush/backend/zigbee/mqtt.py
Normal file
212
platypush/backend/zigbee/mqtt.py
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
import json
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from platypush.backend.mqtt import MqttBackend
|
||||||
|
from platypush.context import get_plugin
|
||||||
|
from platypush.message.event.zigbee.mqtt import ZigbeeMqttOnlineEvent, ZigbeeMqttOfflineEvent, \
|
||||||
|
ZigbeeMqttDevicePropertySetEvent, ZigbeeMqttDevicePairingEvent, ZigbeeMqttDeviceConnectedEvent, \
|
||||||
|
ZigbeeMqttDeviceBannedEvent, ZigbeeMqttDeviceRemovedEvent, ZigbeeMqttDeviceRemovedFailedEvent, \
|
||||||
|
ZigbeeMqttDeviceWhitelistedEvent, ZigbeeMqttDeviceRenamedEvent, ZigbeeMqttDeviceBindEvent, \
|
||||||
|
ZigbeeMqttDeviceUnbindEvent, ZigbeeMqttGroupAddedEvent, ZigbeeMqttGroupAddedFailedEvent, \
|
||||||
|
ZigbeeMqttGroupRemovedEvent, ZigbeeMqttGroupRemovedFailedEvent, ZigbeeMqttGroupRemoveAllEvent, \
|
||||||
|
ZigbeeMqttGroupRemoveAllFailedEvent, ZigbeeMqttErrorEvent
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttBackend(MqttBackend):
|
||||||
|
"""
|
||||||
|
Listen for events on a zigbee2mqtt service.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttOnlineEvent` when the service comes online.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttOfflineEvent` when the service goes offline.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDevicePropertySetEvent` when the properties of a
|
||||||
|
connected device change.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDevicePairingEvent` when a device is pairing.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceConnectedEvent` when a device connects
|
||||||
|
to the network.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceBannedEvent` when a device is banned
|
||||||
|
from the network.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRemovedEvent` when a device is removed
|
||||||
|
from the network.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRemovedFailedEvent` when a request to
|
||||||
|
remove a device from the network fails.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceWhitelistedEvent` when a device is
|
||||||
|
whitelisted on the network.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceRenamedEvent` when a device is
|
||||||
|
renamed on the network.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceBindEvent` when a device bind event
|
||||||
|
occurs.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttDeviceUnbindEvent` when a device unbind event
|
||||||
|
occurs.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupAddedEvent` when a group is added.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupAddedFailedEvent` when a request to
|
||||||
|
add a new group fails.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemovedEvent` when a group is removed.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemovedFailedEvent` when a request to
|
||||||
|
remove a group fails.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemoveAllEvent` when all the devices
|
||||||
|
are removed from a group.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttGroupRemoveAllFailedEvent` when a request to
|
||||||
|
remove all the devices from a group fails.
|
||||||
|
* :class:`platypush.message.event.zigbee.mqtt.ZigbeeMqttErrorEvent` when an internal error occurs
|
||||||
|
on the zigbee2mqtt service.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **paho-mqtt** (``pip install paho-mqtt``)
|
||||||
|
* The :class:`platypush.plugins.zigbee.mqtt.ZigbeeMqttPlugin` plugin configured.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, host: Optional[str] = None, port: Optional[int] = None, base_topic='zigbee2mqtt',
|
||||||
|
tls_cafile: Optional[str] = None, tls_certfile: Optional[str] = None,
|
||||||
|
tls_keyfile: Optional[str] = None, tls_version: Optional[str] = None,
|
||||||
|
tls_ciphers: Optional[str] = None, username: Optional[str] = None,
|
||||||
|
password: Optional[str] = None, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
:param host: MQTT broker host (default: host configured on the ``zigbee.mqtt`` plugin).
|
||||||
|
:param port: MQTT broker port (default: 1883).
|
||||||
|
:param base_topic: Prefix of the topics published by zigbee2mqtt (default: '``zigbee2mqtt``').
|
||||||
|
:param tls_cafile: If TLS/SSL is enabled on the MQTT server and the certificate requires a certificate authority
|
||||||
|
to authenticate it, `ssl_cafile` will point to the provided ca.crt file (default: None)
|
||||||
|
:param tls_certfile: If TLS/SSL is enabled on the MQTT server and a client certificate it required, specify it
|
||||||
|
here (default: None)
|
||||||
|
:param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a client certificate key it required,
|
||||||
|
specify it here (default: None) :type tls_keyfile: str
|
||||||
|
:param tls_version: If TLS/SSL is enabled on the MQTT server and it requires a certain TLS version, specify it
|
||||||
|
here (default: None)
|
||||||
|
:param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an explicit list of supported ciphers is
|
||||||
|
required, specify it here (default: None)
|
||||||
|
:param username: Specify it if the MQTT server requires authentication (default: None)
|
||||||
|
:param password: Specify it if the MQTT server requires authentication (default: None)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if host:
|
||||||
|
self.base_topic = base_topic
|
||||||
|
listeners = [{
|
||||||
|
'host': host,
|
||||||
|
'port': port or self._default_mqtt_port,
|
||||||
|
'tls_cafile': tls_cafile,
|
||||||
|
'tls_certfile': tls_certfile,
|
||||||
|
'tls_ciphers': tls_ciphers,
|
||||||
|
'tls_keyfile': tls_keyfile,
|
||||||
|
'tls_version': tls_version,
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
'topics': [
|
||||||
|
base_topic + '/' + topic
|
||||||
|
for topic in ['bridge/state', 'bridge/log']
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
else:
|
||||||
|
plugin = get_plugin('zigbee.mqtt')
|
||||||
|
self.base_topic = plugin.base_topic
|
||||||
|
listeners = [{
|
||||||
|
'host': plugin.host,
|
||||||
|
'port': plugin.port or self._default_mqtt_port,
|
||||||
|
'tls_cafile': plugin.tls_cafile,
|
||||||
|
'tls_certfile': plugin.tls_certfile,
|
||||||
|
'tls_ciphers': plugin.tls_ciphers,
|
||||||
|
'username': plugin.username,
|
||||||
|
'password': plugin.password,
|
||||||
|
'topics': [
|
||||||
|
plugin.base_topic + '/' + topic
|
||||||
|
for topic in ['bridge/state', 'bridge/log']
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
|
||||||
|
super().__init__(subscribe_default_topic=False, listeners=listeners, *args, **kwargs)
|
||||||
|
self._devices = {}
|
||||||
|
|
||||||
|
def _process_state_message(self, client, msg):
|
||||||
|
if msg == 'online':
|
||||||
|
evt = ZigbeeMqttOnlineEvent
|
||||||
|
self._refresh_devices(client)
|
||||||
|
elif msg == 'offline':
|
||||||
|
evt = ZigbeeMqttOfflineEvent
|
||||||
|
self.logger.warning('zigbee2mqtt service is offline')
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
# noinspection PyProtectedMember
|
||||||
|
self.bus.post(evt(host=client._host, port=client._port))
|
||||||
|
|
||||||
|
def _refresh_devices(self, client):
|
||||||
|
client.publish(self.base_topic + '/' + 'bridge/config/devices/get')
|
||||||
|
|
||||||
|
def _process_log_message(self, client, msg):
|
||||||
|
msg_type = msg.get('type')
|
||||||
|
msg = msg.get('message')
|
||||||
|
# noinspection PyProtectedMember
|
||||||
|
args = {'host': client._host, 'port': client._port}
|
||||||
|
|
||||||
|
if msg_type == 'devices':
|
||||||
|
devices = {}
|
||||||
|
for dev in (msg or []):
|
||||||
|
devices[dev['friendly_name']] = dev
|
||||||
|
client.subscribe(self.base_topic + '/' + dev['friendly_name'])
|
||||||
|
|
||||||
|
self._devices = devices
|
||||||
|
elif msg_type == 'pairing':
|
||||||
|
self.bus.post(ZigbeeMqttDevicePairingEvent(device=msg, **args))
|
||||||
|
elif msg_type == 'device_connected':
|
||||||
|
self.bus.post(ZigbeeMqttDeviceConnectedEvent(device=msg, **args))
|
||||||
|
self._refresh_devices(client)
|
||||||
|
elif msg_type in ['device_ban', 'device_banned']:
|
||||||
|
self.bus.post(ZigbeeMqttDeviceBannedEvent(device=msg, **args))
|
||||||
|
elif msg_type in ['device_removed', 'device_force_removed']:
|
||||||
|
force = msg_type == 'device_force_removed'
|
||||||
|
self.bus.post(ZigbeeMqttDeviceRemovedEvent(device=msg, force=force, **args))
|
||||||
|
elif msg_type in ['device_removed_failed', 'device_force_removed_failed']:
|
||||||
|
force = msg_type == 'device_force_removed_failed'
|
||||||
|
self.bus.post(ZigbeeMqttDeviceRemovedFailedEvent(device=msg, force=force, **args))
|
||||||
|
elif msg_type == 'device_whitelisted':
|
||||||
|
self.bus.post(ZigbeeMqttDeviceWhitelistedEvent(device=msg, **args))
|
||||||
|
elif msg_type == 'device_renamed':
|
||||||
|
self.bus.post(ZigbeeMqttDeviceRenamedEvent(device=msg, **args))
|
||||||
|
self._refresh_devices(client)
|
||||||
|
elif msg_type == 'device_bind':
|
||||||
|
self.bus.post(ZigbeeMqttDeviceBindEvent(device=msg, **args))
|
||||||
|
elif msg_type == 'device_unbind':
|
||||||
|
self.bus.post(ZigbeeMqttDeviceUnbindEvent(device=msg, **args))
|
||||||
|
elif msg_type == 'device_group_add':
|
||||||
|
self.bus.post(ZigbeeMqttGroupAddedEvent(group=msg, **args))
|
||||||
|
elif msg_type == 'device_group_add_failed':
|
||||||
|
self.bus.post(ZigbeeMqttGroupAddedFailedEvent(group=msg, **args))
|
||||||
|
elif msg_type == 'device_group_remove':
|
||||||
|
self.bus.post(ZigbeeMqttGroupRemovedEvent(group=msg, **args))
|
||||||
|
elif msg_type == 'device_group_remove_failed':
|
||||||
|
self.bus.post(ZigbeeMqttGroupRemovedFailedEvent(group=msg, **args))
|
||||||
|
elif msg_type == 'device_group_remove_all':
|
||||||
|
self.bus.post(ZigbeeMqttGroupRemoveAllEvent(group=msg, **args))
|
||||||
|
elif msg_type == 'device_group_remove_all_failed':
|
||||||
|
self.bus.post(ZigbeeMqttGroupRemoveAllFailedEvent(group=msg, **args))
|
||||||
|
elif msg_type == 'zigbee_publish_error':
|
||||||
|
self.logger.warning('zigbee2mqtt internal error: {}'.format(msg))
|
||||||
|
self.bus.post(ZigbeeMqttErrorEvent(error=msg, **args))
|
||||||
|
|
||||||
|
def on_mqtt_message(self):
|
||||||
|
def handler(client, _, msg):
|
||||||
|
topic = msg.topic[len(self.base_topic)+1:]
|
||||||
|
data = msg.payload.decode()
|
||||||
|
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
data = json.loads(data)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if topic == 'bridge/state':
|
||||||
|
self._process_state_message(client, data)
|
||||||
|
elif topic == 'bridge/log':
|
||||||
|
self._process_log_message(client, data)
|
||||||
|
else:
|
||||||
|
# noinspection PyProtectedMember
|
||||||
|
self.bus.post(ZigbeeMqttDevicePropertySetEvent(host=client._host, port=client._port,
|
||||||
|
device=topic, properties=data))
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
0
platypush/message/event/zigbee/__init__.py
Normal file
0
platypush/message/event/zigbee/__init__.py
Normal file
162
platypush/message/event/zigbee/mqtt.py
Normal file
162
platypush/message/event/zigbee/mqtt.py
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
|
from platypush.message.event import Event
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttEvent(Event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttOnlineEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a zigbee2mqtt service goes online.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttOfflineEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a zigbee2mqtt service goes offline.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDevicePropertySetEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a the properties of a Zigbee connected devices (state, brightness, alert etc.) change.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device: str, properties: Dict[str, Any], *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, properties=properties, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDevicePairingEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a device is pairing to the network.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDeviceConnectedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a device connects to the network.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDeviceBannedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a device is banned from the network.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDeviceRemovedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a device is removed from the network.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device=None, force=False, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, force=force, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDeviceRemovedFailedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when the removal of a device from the network failed.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDeviceWhitelistedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a device is whitelisted on the network.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDeviceRenamedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a device is renamed on the network.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDeviceBindEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a device bind occurs on the network.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttDeviceUnbindEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a device bind occurs on the network.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, device=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttGroupAddedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a group is added.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, group=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttGroupAddedFailedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a request to add a group fails.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, group=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttGroupRemovedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a group is removed.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, group=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttGroupRemovedFailedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a request to remove a group fails.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, group=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttGroupRemoveAllEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when all the devices are removed from a group.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, group=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttGroupRemoveAllFailedEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when a request to remove all the devices from a group fails.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, group=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, device=device, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttErrorEvent(ZigbeeMqttEvent):
|
||||||
|
"""
|
||||||
|
Triggered when an error happens on the zigbee2mqtt service.
|
||||||
|
"""
|
||||||
|
def __init__(self, host: str, port: int, error=None, *args, **kwargs):
|
||||||
|
super().__init__(*args, host=host, port=port, error=error, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
|
@ -1,5 +1,9 @@
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from typing import Any, Optional, IO
|
||||||
|
|
||||||
from platypush.message import Message
|
from platypush.message import Message
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
|
@ -9,12 +13,17 @@ class MqttPlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
This plugin allows you to send custom message to a message queue compatible
|
This plugin allows you to send custom message to a message queue compatible
|
||||||
with the MQTT protocol, see http://mqtt.org/
|
with the MQTT protocol, see http://mqtt.org/
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **paho-mqtt** (``pip install paho-mqtt``)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host=None, port=1883, tls_cafile=None,
|
def __init__(self, host=None, port=1883, tls_cafile=None,
|
||||||
tls_certfile=None, tls_keyfile=None,
|
tls_certfile=None, tls_keyfile=None,
|
||||||
tls_version=None, tls_ciphers=None, username=None,
|
tls_version=None, tls_ciphers=None, username=None,
|
||||||
password=None, *args, **kwargs):
|
password=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param host: If set, MQTT messages will by default routed to this host unless overridden in `send_message` (default: None)
|
:param host: If set, MQTT messages will by default routed to this host unless overridden in `send_message` (default: None)
|
||||||
:type host: str
|
:type host: str
|
||||||
|
@ -44,7 +53,7 @@ class MqttPlugin(Plugin):
|
||||||
:type password: str
|
:type password: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
|
@ -62,102 +71,117 @@ class MqttPlugin(Plugin):
|
||||||
self.tls_version = tls_version
|
self.tls_version = tls_version
|
||||||
self.tls_ciphers = tls_ciphers
|
self.tls_ciphers = tls_ciphers
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def send_message(self, topic, msg, host=None, port=1883, tls_cafile=None,
|
def publish(self, topic: str, msg: Any, host: Optional[str] = None, port: int = 1883,
|
||||||
tls_certfile=None, tls_keyfile=None,
|
reply_topic: Optional[str] = None, timeout: int = 60,
|
||||||
tls_version=None, tls_ciphers=None, username=None,
|
tls_cafile: Optional[str] = None, tls_certfile: Optional[str] = None,
|
||||||
password=None, *args, **kwargs):
|
tls_keyfile: Optional[str] = None, tls_version: Optional[str] = None,
|
||||||
|
tls_ciphers: Optional[str] = None, username: Optional[str] = None,
|
||||||
|
password: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
Sends a message to a topic/channel.
|
Sends a message to a topic.
|
||||||
|
|
||||||
:param topic: Topic/channel where the message will be delivered
|
: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.
|
||||||
:param msg: Message to be sent. It can be a list, a dict, or a Message object
|
:param port: MQTT broker port (default: 1883).
|
||||||
|
:param reply_topic: If a ``reply_topic`` is specified, then the action will wait for a response on this topic.
|
||||||
:param host: MQTT broker hostname/IP
|
:param timeout: If ``reply_topic`` is set, use this parameter to specify the maximum amount of time to
|
||||||
:type host: str
|
wait for a response (default: 60 seconds).
|
||||||
|
:param tls_cafile: If TLS/SSL is enabled on the MQTT server and the certificate requires a certificate authority
|
||||||
:param port: MQTT broker port (default: 1883)
|
to authenticate it, `ssl_cafile` will point to the provided ca.crt file (default: None).
|
||||||
:type port: int
|
:param tls_certfile: If TLS/SSL is enabled on the MQTT server and a client certificate it required, specify it
|
||||||
|
here (default: None).
|
||||||
:param tls_cafile: If TLS/SSL is enabled on the MQTT server and the certificate requires a certificate authority to authenticate it, `ssl_cafile` will point to the provided ca.crt file (default: None)
|
:param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a client certificate key it required, specify
|
||||||
:type tls_cafile: str
|
it here (default: None).
|
||||||
|
:param tls_version: If TLS/SSL is enabled on the MQTT server and it requires a certain TLS version, specify it
|
||||||
:param tls_certfile: If TLS/SSL is enabled on the MQTT server and a client certificate it required, specify it here (default: None)
|
here (default: None).
|
||||||
:type tls_certfile: str
|
:param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an explicit list of supported ciphers is
|
||||||
|
required, specify it here (default: None).
|
||||||
:param tls_keyfile: If TLS/SSL is enabled on the MQTT server and a client certificate key it required, specify it here (default: None)
|
:param username: Specify it if the MQTT server requires authentication (default: None).
|
||||||
:type tls_keyfile: str
|
:param password: Specify it if the MQTT server requires authentication (default: None).
|
||||||
|
|
||||||
:param tls_version: If TLS/SSL is enabled on the MQTT server and it requires a certain TLS version, specify it here (default: None)
|
|
||||||
:type tls_version: str
|
|
||||||
|
|
||||||
:param tls_ciphers: If TLS/SSL is enabled on the MQTT server and an explicit list of supported ciphers is required, specify it here (default: None)
|
|
||||||
:type tls_ciphers: str
|
|
||||||
|
|
||||||
:param username: Specify it if the MQTT server requires authentication (default: None)
|
|
||||||
:type username: str
|
|
||||||
|
|
||||||
:param password: Specify it if the MQTT server requires authentication (default: None)
|
|
||||||
:type password: str
|
|
||||||
"""
|
"""
|
||||||
|
from paho.mqtt.client import Client
|
||||||
import paho.mqtt.publish as publisher
|
|
||||||
|
|
||||||
if not host and not self.host:
|
if not host and not self.host:
|
||||||
raise RuntimeError('No host specified and no default host configured')
|
raise RuntimeError('No host specified and no default host configured')
|
||||||
|
|
||||||
publisher_args = {
|
if not host:
|
||||||
'hostname': host or self.host,
|
tls_cafile = self.tls_cafile
|
||||||
'port': port or self.port,
|
tls_certfile = self.tls_certfile
|
||||||
}
|
tls_keyfile = self.tls_keyfile
|
||||||
|
tls_version = self.tls_version
|
||||||
|
tls_ciphers = self.tls_ciphers
|
||||||
|
username = self.username
|
||||||
|
password = self.password
|
||||||
|
|
||||||
|
client = Client()
|
||||||
|
|
||||||
if host:
|
|
||||||
if username and password:
|
if username and password:
|
||||||
publisher_args['auth'] = {
|
client.username_pw_set(username, password)
|
||||||
'username': username,
|
|
||||||
'password': password,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
if self.username and self.password:
|
|
||||||
publisher_args['auth'] = {
|
|
||||||
'username': username,
|
|
||||||
'password': password,
|
|
||||||
}
|
|
||||||
|
|
||||||
if host:
|
|
||||||
if tls_cafile:
|
if tls_cafile:
|
||||||
publisher_args['tls'] = { 'ca_certs': tls_cafile }
|
client.tls_set(ca_certs=tls_cafile, certfile=tls_certfile, keyfile=tls_keyfile, tls_version=tls_version,
|
||||||
if tls_certfile:
|
ciphers=tls_ciphers)
|
||||||
publisher_args['tls']['certfile'] = tls_certfile
|
|
||||||
if tls_keyfile:
|
|
||||||
publisher_args['tls']['keyfile'] = tls_keyfile
|
|
||||||
if tls_version:
|
|
||||||
publisher_args['tls']['tls_version'] = tls_version
|
|
||||||
if tls_ciphers:
|
|
||||||
publisher_args['tls']['ciphers'] = tls_ciphers
|
|
||||||
else:
|
|
||||||
if self.tls_cafile:
|
|
||||||
publisher_args['tls'] = { 'ca_certs': self.tls_cafile }
|
|
||||||
if self.tls_certfile:
|
|
||||||
publisher_args['tls']['certfile'] = self.tls_certfile
|
|
||||||
if self.tls_keyfile:
|
|
||||||
publisher_args['tls']['keyfile'] = self.tls_keyfile
|
|
||||||
if self.tls_version:
|
|
||||||
publisher_args['tls']['tls_version'] = self.tls_version
|
|
||||||
if self.tls_ciphers:
|
|
||||||
publisher_args['tls']['ciphers'] = self.tls_ciphers
|
|
||||||
|
|
||||||
try: msg = json.dumps(msg)
|
# Try to parse it as a platypush message or dump it to JSON from a dict/list
|
||||||
except: pass
|
if isinstance(msg, dict) or isinstance(msg, list):
|
||||||
|
msg = json.dumps(msg)
|
||||||
|
|
||||||
try: msg = Message.build(json.loads(msg))
|
# noinspection PyBroadException
|
||||||
except: pass
|
try:
|
||||||
|
msg = Message.build(json.loads(msg))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
publisher.single(topic, str(msg), **publisher_args)
|
client.connect(host, port, keepalive=timeout)
|
||||||
|
response_buffer = io.BytesIO()
|
||||||
|
|
||||||
|
try:
|
||||||
|
response_received = threading.Event()
|
||||||
|
|
||||||
|
if reply_topic:
|
||||||
|
client.on_message = self._response_callback(reply_topic=reply_topic,
|
||||||
|
event=response_received,
|
||||||
|
buffer=response_buffer)
|
||||||
|
client.subscribe(reply_topic)
|
||||||
|
|
||||||
|
client.publish(topic, str(msg))
|
||||||
|
if not reply_topic:
|
||||||
|
return
|
||||||
|
|
||||||
|
client.loop_start()
|
||||||
|
ok = response_received.wait(timeout=timeout)
|
||||||
|
if not ok:
|
||||||
|
raise TimeoutError('Response timed out')
|
||||||
|
return response_buffer.getvalue()
|
||||||
|
finally:
|
||||||
|
response_buffer.close()
|
||||||
|
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
client.loop_stop()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
client.disconnect()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _response_callback(reply_topic: str, event: threading.Event, buffer: IO[bytes]):
|
||||||
|
def on_message(client, _, msg):
|
||||||
|
if msg.topic != reply_topic:
|
||||||
|
return
|
||||||
|
|
||||||
|
buffer.write(msg.payload)
|
||||||
|
client.loop_stop()
|
||||||
|
event.set()
|
||||||
|
return on_message
|
||||||
|
|
||||||
|
@action
|
||||||
|
def send_message(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Alias for :meth:`platypush.plugins.mqtt.MqttPlugin.publish`.
|
||||||
|
"""
|
||||||
|
return self.publish(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
0
platypush/plugins/zigbee/__init__.py
Normal file
0
platypush/plugins/zigbee/__init__.py
Normal file
463
platypush/plugins/zigbee/mqtt.py
Normal file
463
platypush/plugins/zigbee/mqtt.py
Normal file
|
@ -0,0 +1,463 @@
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from typing import Optional, List, Any, Dict
|
||||||
|
|
||||||
|
from platypush.plugins.mqtt import MqttPlugin, action
|
||||||
|
|
||||||
|
|
||||||
|
class ZigbeeMqttPlugin(MqttPlugin):
|
||||||
|
"""
|
||||||
|
This plugin allows you to interact with Zigbee devices over MQTT through any Zigbee sniffer and
|
||||||
|
`zigbee2mqtt <https://www.zigbee2mqtt.io/>`_.
|
||||||
|
|
||||||
|
In order to get started you'll need:
|
||||||
|
|
||||||
|
- A Zigbee USB adapter/sniffer (in this example I'll use the `CC2531 <https://hackaday.io/project/163487-zigbee-cc2531-smart-home-usb-adapter>`_.
|
||||||
|
- A Zigbee debugger/emulator + downloader cable (only to flash the firmware).
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
|
||||||
|
- Install `cc-tool <https://github.com/dashesy/cc-tool>`_ either from sources or from a package manager.
|
||||||
|
- Connect the Zigbee to your PC/RaspberryPi in this way: USB -> CC debugger -> downloader cable -> CC2531 -> USB.
|
||||||
|
The debugger and the adapter should be connected _at the same time_. If the later ``cc-tool`` command throws up
|
||||||
|
an error, put the device in sync while connected by pressing the _Reset_ button on the debugger.
|
||||||
|
- Check where the device is mapped. On Linux it will usually be ``/dev/ttyACM0``.
|
||||||
|
- Download the latest `Z-Stack firmware <https://github.com/Koenkk/Z-Stack-firmware/tree/master/coordinator>`_ to your device.
|
||||||
|
Instructions for a CC2531 device:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
wget https://github.com/Koenkk/Z-Stack-firmware/raw/master/coordinator/Z-Stack_Home_1.2/bin/default/CC2531_DEFAULT_20190608.zip
|
||||||
|
unzip CC2531_DEFAULT_20190608.zip
|
||||||
|
[sudo] cc-tool -e -w CC2531ZNP-Prod.hex
|
||||||
|
|
||||||
|
- You can disconnect your debugger and downloader cable once the firmware is flashed.
|
||||||
|
|
||||||
|
- Install ``zigbee2mqtt``. First install a node/npm environment, then either install ``zigbee2mqtt`` manually or through your
|
||||||
|
package manager. Manual instructions:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
# Clone zigbee2mqtt repository
|
||||||
|
[sudo] git clone https://github.com/Koenkk/zigbee2mqtt.git /opt/zigbee2mqtt
|
||||||
|
[sudo] chown -R pi:pi /opt/zigbee2mqtt # Or whichever is your user
|
||||||
|
|
||||||
|
# Install dependencies (as user "pi")
|
||||||
|
cd /opt/zigbee2mqtt
|
||||||
|
npm install
|
||||||
|
|
||||||
|
- You need to have an MQTT broker running somewhere. If not, you can install
|
||||||
|
`Mosquitto <https://mosquitto.org/>`_ through your package manager on any device in your network.
|
||||||
|
|
||||||
|
- Edit the ``/opt/zigbee2mqtt/data/configuration.yaml`` file to match the configuration of your MQTT broker:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# MQTT settings
|
||||||
|
mqtt:
|
||||||
|
# MQTT base topic for zigbee2mqtt MQTT messages
|
||||||
|
base_topic: zigbee2mqtt
|
||||||
|
# MQTT server URL
|
||||||
|
server: 'mqtt://localhost'
|
||||||
|
# MQTT server authentication, uncomment if required:
|
||||||
|
# user: my_user
|
||||||
|
# password: my_password
|
||||||
|
|
||||||
|
- Also make sure that ``permit_join`` is set to ``True``, in order to allow Zigbee devices to join the network
|
||||||
|
while you're configuring it. It's equally important to set ``permit_join`` to ``False`` once you have
|
||||||
|
configured your network, to prevent accidental/malignant joins from outer Zigbee devices.
|
||||||
|
|
||||||
|
- Start the ``zigbee2mqtt`` daemon on your device (the
|
||||||
|
`official documentation <https://www.zigbee2mqtt.io/getting_started/running_zigbee2mqtt.html#5-optional-running-as-a-daemon-with-systemctl>`_
|
||||||
|
also contains instructions on how to configure it as a ``systemd`` service:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
cd /opt/zigbee2mqtt
|
||||||
|
npm start
|
||||||
|
|
||||||
|
- If you have Zigbee devices that are paired to other bridges, unlink them or do a factory reset to pair them
|
||||||
|
to your new bridge.
|
||||||
|
|
||||||
|
- If it all goes fine, once the daemon is running and a new device is found you should see traces like this in
|
||||||
|
the output of ``zigbee2mqtt``::
|
||||||
|
|
||||||
|
zigbee2mqtt:info 2019-11-09T12:19:56: Successfully interviewed '0x00158d0001dc126a', device has successfully been paired
|
||||||
|
|
||||||
|
- You are now ready to use this integration.
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
|
||||||
|
* **paho-mqtt** (``pip install paho-mqtt``)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, host: str, port: int = 1883, base_topic: str = 'zigbee2mqtt', timeout: int = 60,
|
||||||
|
tls_certfile: Optional[str] = None, tls_keyfile: Optional[str] = None,
|
||||||
|
tls_version: Optional[str] = None, tls_ciphers: Optional[str] = None,
|
||||||
|
username: Optional[str] = None, password: Optional[str] = None, **kwargs):
|
||||||
|
"""
|
||||||
|
:param host: Default MQTT broker where ``zigbee2mqtt`` publishes its messages.
|
||||||
|
:param port: Broker listen port (default: 1883).
|
||||||
|
:param base_topic: Topic prefix, as specified in ``/opt/zigbee2mqtt/data/configuration.yaml``
|
||||||
|
(default: '``base_topic``').
|
||||||
|
:param timeout: If the command expects from a response, then this timeout value will be used
|
||||||
|
(default: 60 seconds).
|
||||||
|
:param tls_cafile: If the connection requires TLS/SSL, specify the certificate authority file
|
||||||
|
(default: None)
|
||||||
|
:param tls_certfile: If the connection requires TLS/SSL, specify the certificate file (default: None)
|
||||||
|
:param tls_keyfile: If the connection requires TLS/SSL, specify the key file (default: None)
|
||||||
|
:param tls_version: If the connection requires TLS/SSL, specify the minimum TLS supported version
|
||||||
|
(default: None)
|
||||||
|
:param tls_ciphers: If the connection requires TLS/SSL, specify the supported ciphers (default: None)
|
||||||
|
:param username: If the connection requires user authentication, specify the username (default: None)
|
||||||
|
:param password: If the connection requires user authentication, specify the password (default: None)
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().__init__(host=host, port=port, tls_certfile=tls_certfile, tls_keyfile=tls_keyfile,
|
||||||
|
tls_version=tls_version, tls_ciphers=tls_ciphers, username=username,
|
||||||
|
password=password, **kwargs)
|
||||||
|
|
||||||
|
self.base_topic = base_topic
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
def _mqtt_args(self, host: Optional[str] = None, **kwargs):
|
||||||
|
if not host:
|
||||||
|
return {
|
||||||
|
'host': self.host,
|
||||||
|
'port': self.port,
|
||||||
|
'timeout': self.timeout,
|
||||||
|
'tls_certfile': self.tls_certfile,
|
||||||
|
'tls_keyfile': self.tls_keyfile,
|
||||||
|
'tls_version': self.tls_version,
|
||||||
|
'tls_ciphers': self.tls_ciphers,
|
||||||
|
'username': self.username,
|
||||||
|
'password': self.password,
|
||||||
|
}
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def _topic(self, topic):
|
||||||
|
return self.base_topic + '/' + topic
|
||||||
|
|
||||||
|
@action
|
||||||
|
def devices(self, **kwargs) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Get the list of devices registered to the service.
|
||||||
|
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
|
||||||
|
:return: List of paired devices. Example output:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"dateCode": "20190608",
|
||||||
|
"friendly_name": "Coordinator",
|
||||||
|
"ieeeAddr": "0x00123456789abcde",
|
||||||
|
"lastSeen": 1579640601215,
|
||||||
|
"networkAddress": 0,
|
||||||
|
"softwareBuildID": "zStack12",
|
||||||
|
"type": "Coordinator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dateCode": "20160906",
|
||||||
|
"friendly_name": "My Lightbulb",
|
||||||
|
"hardwareVersion": 1,
|
||||||
|
"ieeeAddr": "0x00123456789abcdf",
|
||||||
|
"lastSeen": 1579595191623,
|
||||||
|
"manufacturerID": 4107,
|
||||||
|
"manufacturerName": "Philips",
|
||||||
|
"model": "8718696598283",
|
||||||
|
"modelID": "LTW013",
|
||||||
|
"networkAddress": 52715,
|
||||||
|
"powerSource": "Mains (single phase)",
|
||||||
|
"softwareBuildID": "1.15.2_r19181",
|
||||||
|
"type": "Router"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.publish(
|
||||||
|
topic=self._topic('bridge/config/devices/get'), msg='',
|
||||||
|
reply_topic=self._topic('bridge/config/devices'),
|
||||||
|
**self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
def _permit_join_timeout_callback(self, permit: bool, **kwargs):
|
||||||
|
def callback():
|
||||||
|
self.logger.info('Restoring permit_join state to {}'.format(permit))
|
||||||
|
self.permit_join(permit, **kwargs)
|
||||||
|
return callback
|
||||||
|
|
||||||
|
@action
|
||||||
|
def permit_join(self, permit: bool = True, timeout: Optional[float] = None, **kwargs):
|
||||||
|
"""
|
||||||
|
Enable/disable devices from joining the network. This is not persistent (will not be saved to
|
||||||
|
``configuration.yaml``).
|
||||||
|
|
||||||
|
:param permit: Set to True to allow joins, False otherwise.
|
||||||
|
:param timeout: Allow/disallow joins only for this amount of time.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/config/permit_join'), msg=permit, **self._mqtt_args(**kwargs))
|
||||||
|
if timeout:
|
||||||
|
threading.Timer(timeout, self._permit_join_timeout_callback(not permit, **kwargs)).start()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def reset(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Reset the adapter.
|
||||||
|
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/config/reset'), msg='', **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def factory_reset(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Perform a factory reset of the device. Of course, you should only do it if you know what you're doing,
|
||||||
|
as you will lose all the paired devices and may also lose the Z-Stack firmware.
|
||||||
|
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/config/factory_reset'), msg='', **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def log_level(self, level: str, **kwargs):
|
||||||
|
"""
|
||||||
|
Change the log level at runtime. This change will not be persistent.
|
||||||
|
|
||||||
|
:param level: Possible values: 'debug', 'info', 'warn', 'error'.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/config/log_level'), msg=level, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def device_set_option(self, device: str, option: str, value: Any, **kwargs):
|
||||||
|
"""
|
||||||
|
Change the options of a device. Options can only be changed, not added or deleted.
|
||||||
|
|
||||||
|
:param device: Display name of the device.
|
||||||
|
:param option: Option name.
|
||||||
|
:param value: New value.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/config/device_options'), msg={
|
||||||
|
'friendly_name': device,
|
||||||
|
'options': {
|
||||||
|
option: value,
|
||||||
|
}
|
||||||
|
}, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def device_remove(self, device: str, force: bool = False, **kwargs):
|
||||||
|
"""
|
||||||
|
Remove a device from the network.
|
||||||
|
|
||||||
|
:param device: Display name of the device.
|
||||||
|
:param force: Force the remove also if the removal wasn't acknowledged by the device. Note: a forced remove
|
||||||
|
only removes the entry from the internal database, but the device is likely to connect again when
|
||||||
|
restarted unless it's factory reset (default: False).
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
topic = self._topic('bridge/config/{}remove'.format('force_' if force else ''))
|
||||||
|
self.publish(topic=topic, msg=device, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def device_ban(self, device: str, **kwargs):
|
||||||
|
"""
|
||||||
|
Ban a device from the network.
|
||||||
|
|
||||||
|
:param device: Display name of the device.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/config/ban'), msg=device, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def device_whitelist(self, device: str, **kwargs):
|
||||||
|
"""
|
||||||
|
Whitelist a device on the network. Note: once at least a device is whitelisted, all the other non-whitelisted
|
||||||
|
devices will be removed from the network.
|
||||||
|
|
||||||
|
:param device: Display name of the device.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/config/whitelist'), msg=device, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def device_rename(self, name: str, device: Optional[str] = None, **kwargs):
|
||||||
|
"""
|
||||||
|
Rename a device on the network.
|
||||||
|
|
||||||
|
:param name: New name.
|
||||||
|
:param device: Current name of the device to rename. If no name is specified then the rename will
|
||||||
|
affect the last device that joined the network.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(
|
||||||
|
topic=self._topic('bridge/config/rename{}'.format('_last' if not device else '')),
|
||||||
|
msg={'old': device, 'new': name} if device else name,
|
||||||
|
**self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
# noinspection PyShadowingBuiltins
|
||||||
|
@action
|
||||||
|
def device_get(self, device: str, property: Optional[str] = None, **kwargs) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get the properties of a device. The returned keys vary depending on the device. For example, a light bulb
|
||||||
|
may have the "``state``" and "``brightness``" properties, while an environment sensor may have the
|
||||||
|
"``temperature``" and "``humidity``" properties, and so on.
|
||||||
|
|
||||||
|
:param device: Display name of the device.
|
||||||
|
:param property: Name of the property that should be retrieved (default: all).
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
:return: Key->value map of the device properties.
|
||||||
|
"""
|
||||||
|
properties = self.publish(topic=self._topic(device + '/get'),
|
||||||
|
reply_topic=self._topic(device), msg='', **self._mqtt_args(**kwargs)).output
|
||||||
|
|
||||||
|
if property:
|
||||||
|
assert property in properties, 'No such property: ' + property
|
||||||
|
return {property: properties[property]}
|
||||||
|
|
||||||
|
return properties
|
||||||
|
|
||||||
|
# noinspection PyShadowingBuiltins
|
||||||
|
@action
|
||||||
|
def device_set(self, device: str, property: str, value: Any, **kwargs):
|
||||||
|
"""
|
||||||
|
Set a properties on a device. The compatible properties vary depending on the device. For example, a light bulb
|
||||||
|
may have the "``state``" and "``brightness``" properties, while an environment sensor may have the
|
||||||
|
"``temperature``" and "``humidity``" properties, and so on.
|
||||||
|
|
||||||
|
:param device: Display name of the device.
|
||||||
|
:param property: Name of the property that should be set.
|
||||||
|
:param value: New value of the property.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
properties = self.publish(topic=self._topic(device + '/set'),
|
||||||
|
reply_topic=self._topic(device),
|
||||||
|
msg={property: value}, **self._mqtt_args(**kwargs)).output
|
||||||
|
|
||||||
|
if property:
|
||||||
|
assert property in properties, 'No such property: ' + property
|
||||||
|
return {property: properties[property]}
|
||||||
|
|
||||||
|
return properties
|
||||||
|
|
||||||
|
@action
|
||||||
|
def device_groups(self, device: str, **kwargs) -> List[int]:
|
||||||
|
"""
|
||||||
|
List the groups a given device belongs to.
|
||||||
|
|
||||||
|
:param device: Display name of the device.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
:return: List of group IDs the device is linked to.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.publish(topic=self._topic('bridge/device/{}/get_group_membership'.format(device)),
|
||||||
|
reply_topic=self._topic(device), msg=device, **self._mqtt_args(**kwargs)).\
|
||||||
|
output.get('group_list', [])
|
||||||
|
|
||||||
|
@action
|
||||||
|
def group_add(self, name: str, id: Optional[int] = None, **kwargs):
|
||||||
|
"""
|
||||||
|
Add a new group.
|
||||||
|
|
||||||
|
:param name: Display name of the group.
|
||||||
|
:param id: Optional numeric ID (default: auto-generated).
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
args = {'friendly_name': name}
|
||||||
|
if id is not None:
|
||||||
|
args['id'] = id
|
||||||
|
|
||||||
|
self.publish(topic=self._topic('bridge/config/add_group'), msg=args, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def group_remove(self, name: str, **kwargs):
|
||||||
|
"""
|
||||||
|
Remove a group.
|
||||||
|
|
||||||
|
:param name: Display name of the group.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/config/remove_group'), msg=name,
|
||||||
|
**self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def group_add_device(self, group: str, device: str, **kwargs):
|
||||||
|
"""
|
||||||
|
Add a device to a group.
|
||||||
|
|
||||||
|
:param group: Display name of the group.
|
||||||
|
:param device: Display name of the device to be added.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/group/{}/add'.format(group)),
|
||||||
|
msg=device, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def group_remove_device(self, group: str, device: Optional[str] = None, **kwargs):
|
||||||
|
"""
|
||||||
|
Remove a device from a group.
|
||||||
|
|
||||||
|
:param group: Display name of the group.
|
||||||
|
:param device: Display name of the device to be removed. If none is specified then all the devices registered
|
||||||
|
to the specified group will be removed.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/group/{}/remove{}'.format(group, '_all' if not device else '')),
|
||||||
|
msg=device, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def bind_devices(self, source: str, target: str, endpoint: Optional[str] = None, **kwargs):
|
||||||
|
"""
|
||||||
|
Bind two devices. Binding makes it possible that devices can directly control each other without the
|
||||||
|
intervention of zigbee2mqtt or any home automation software. You may want to use this feature to bind
|
||||||
|
for example an IKEA/Philips Hue dimmer switch to a light bulb, or a Zigbee remote to a thermostat.
|
||||||
|
Read more on the `zigbee2mqtt binding page <https://www.zigbee2mqtt.io/information/binding.html>`_.
|
||||||
|
|
||||||
|
:param source: Name of the source device. It can also be a group name, although the support is
|
||||||
|
`still experimental <https://www.zigbee2mqtt.io/information/binding.html#binding-a-group>`_.
|
||||||
|
:param target: Name of the target device.
|
||||||
|
:param endpoint: The target may support multiple endpoints (e.g. 'left', 'down', 'up' etc.). If so,
|
||||||
|
you can bind the source to a specific endpoint on the target device.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/bind/' + source + ('/' + endpoint if endpoint else '')),
|
||||||
|
msg=target, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
@action
|
||||||
|
def unbind_devices(self, source: str, target: str, **kwargs):
|
||||||
|
"""
|
||||||
|
Un-bind two devices.
|
||||||
|
|
||||||
|
:param source: Name of the source device.
|
||||||
|
:param target: Name of the target device.
|
||||||
|
:param kwargs: Extra arguments to be passed to :meth:`platypush.plugins.mqtt.MqttPlugin.publish``
|
||||||
|
(default: query the default configured device).
|
||||||
|
"""
|
||||||
|
self.publish(topic=self._topic('bridge/unbind/' + source),
|
||||||
|
msg=target, **self._mqtt_args(**kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
Loading…
Reference in a new issue