mqttwrapper 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ from .paho_backend import run_script
@@ -0,0 +1,43 @@
1
+ import os
2
+ import logging
3
+ import asyncio
4
+ from warnings import warn
5
+
6
+ from hbmqtt.client import MQTTClient, ClientException
7
+ from hbmqtt.mqtt.constants import QOS_0
8
+
9
+ log = logging.getLogger("mqttwrapper.hbmqtt_backend")
10
+
11
+ warn("hbmqtt backend is deprecated and will be removed in the future.", DeprecationWarning)
12
+ log.warn("hbmqtt backend is deprecated and will be removed in the future.")
13
+
14
+ async def mqtt_loop(broker, topics, callback, context_callback, **kwargs):
15
+ if context_callback is not None:
16
+ kwargs.update(await context_callback())
17
+ client = MQTTClient()
18
+ await client.connect(broker, cleansession=True)
19
+ log.debug("Connected")
20
+ await client.subscribe([(topic, QOS_0) for topic in topics])
21
+ log.debug("Subscribed to %s", topics)
22
+ try:
23
+ while True:
24
+ message = await client.deliver_message()
25
+ packet = message.publish_packet
26
+ topic = packet.variable_header.topic_name
27
+ payload = bytes(packet.payload.data)
28
+ replies = await callback(topic, payload, **kwargs)
29
+ if replies is not None:
30
+ for reply_topic, reply_payload in replies:
31
+ await client.publish(reply_topic, reply_payload)
32
+ except ClientException:
33
+ log.exception("hbmqtt.client.ClientException:")
34
+ raise
35
+
36
+
37
+ def run_script(callback, broker=None, topics=None, context_callback=None, **kwargs):
38
+ if not broker:
39
+ broker = os.environ['MQTT_BROKER']
40
+ if not topics:
41
+ topics = os.environ['MQTT_TOPICS'].split(",")
42
+
43
+ asyncio.get_event_loop().run_until_complete(mqtt_loop(broker, topics, callback, context_callback, **kwargs))
@@ -0,0 +1,64 @@
1
+ import os
2
+ import logging
3
+ import paho.mqtt.client as mqtt
4
+ import threading
5
+
6
+ log = logging.getLogger("mqttwrapper.paho_backend")
7
+
8
+ def on_connect(client, userdata, flags, rc):
9
+ log.debug("Connected")
10
+ for topic in userdata['topics']:
11
+ client.subscribe(topic)
12
+ log.debug("Subscribed to %s", topic)
13
+
14
+ def on_message(client, userdata, msg):
15
+ log.debug("Received message on %s", msg.topic)
16
+ if msg.retain and userdata['ignore_retained']:
17
+ log.debug("Ignoring retained message")
18
+ return
19
+ replies = None
20
+ try:
21
+ replies = userdata['callback'](msg.topic, msg.payload, **userdata['kwargs'])
22
+ except Exception as e:
23
+ log.error("Callback caused exception", exc_info = True)
24
+ log.debug("Callback completed.")
25
+ if replies is not None:
26
+ replies = list(replies)
27
+ log.debug("Received %s replies", len(replies))
28
+ for reply in replies:
29
+ try:
30
+ topic, payload, retain = reply
31
+ except ValueError:
32
+ topic, payload = reply
33
+ retain = False
34
+ log.debug("Publishing '%s' to %s (retain: %s)", payload, topic, retain)
35
+ client.publish(topic, payload, retain=retain)
36
+ log.debug("Published '%s' to %s (retain: %s)", payload, topic, retain)
37
+
38
+ def mqtt_thread(client):
39
+ client.loop_forever()
40
+
41
+ def run_script(callback, broker=None, topics=None, ignore_retained=False, blocking=True, **kwargs):
42
+ if not broker:
43
+ broker = os.environ['MQTT_BROKER']
44
+ if not topics:
45
+ topics = os.environ['MQTT_TOPICS'].split(",")
46
+
47
+ userdata = {
48
+ 'topics': topics,
49
+ 'callback': callback,
50
+ 'ignore_retained': ignore_retained,
51
+ 'kwargs': kwargs
52
+ }
53
+
54
+ client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, userdata=userdata)
55
+ client.on_connect = on_connect
56
+ client.on_message = on_message
57
+ if "//" in broker: # mqtt:// prefix is optional (NB mqtts:// is not supported)
58
+ broker = broker.split("//")[1]
59
+ client.connect(broker)
60
+ if blocking:
61
+ client.loop_forever()
62
+ else:
63
+ t = threading.Thread(target=mqtt_thread, args=(client,), daemon=True)
64
+ t.start()
mqttwrapper/run.py ADDED
@@ -0,0 +1,11 @@
1
+ import sys
2
+
3
+ from .paho_backend import run_script
4
+
5
+ if __name__ == "__main__":
6
+ try:
7
+ from callback import callback
8
+ run_script(callback)
9
+ except ImportError:
10
+ print("""Couldn't import callback function from callback module.
11
+ Please ensure there is a function called 'callback' defined in a file called callback.py in the current directory.""", file=sys.stderr)
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: mqttwrapper
3
+ Version: 0.2.0
4
+ Summary: MQTT utility scripts made easy
5
+ Home-page: https://github.com/davea/mqttwrapper
6
+ Author: Dave Arter
7
+ Author-email: pypi@davea.me
8
+ License: MIT
9
+ Keywords: mqtt
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Software Development :: Build Tools
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.5
16
+ Classifier: Programming Language :: Python :: 3.6
17
+ Classifier: Programming Language :: Python :: 3.7
18
+ License-File: LICENSE
19
+ Requires-Dist: paho-mqtt>=2.0.0
20
+ Provides-Extra: hbmqtt
21
+ Requires-Dist: hbmqtt; extra == "hbmqtt"
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: home-page
27
+ Dynamic: keywords
28
+ Dynamic: license
29
+ Dynamic: license-file
30
+ Dynamic: provides-extra
31
+ Dynamic: requires-dist
32
+ Dynamic: summary
33
+
34
+ mqttwrapper
35
+ ===========
36
+
37
+ A little glue package to make it simple to quickly put together scripts that
38
+ bridge MQTT and other libraries. See examples below.
39
+
40
+ Installation
41
+ ------------
42
+
43
+ Install from PyPI::
44
+
45
+ $ pip install mqttwrapper
46
+
47
+ By default ``paho-mqtt`` will be used as the MQTT library, but you can use
48
+ ``hbmqtt`` if you wish, though support for this is deprecated and will be
49
+ removed in the future. To install, use::
50
+
51
+ $ pip install mqttwrapper[hbmqtt]
52
+
53
+ Usage
54
+ -----
55
+
56
+ ``mqttwrapper.run_script`` handles the setup/maintenance of the MQTT connection
57
+ and subscriptions to topics, and calls a callback function when messages are
58
+ received. It can run in blocking mode where it will take care of the MQTT
59
+ client handling or in non blocking mode where a new thread takes care this
60
+ leaving your main thread free for other things.
61
+
62
+ The simplest script looks something like this::
63
+
64
+ from mqttwrapper import run_script
65
+
66
+ def callback(topic, payload):
67
+ print("Received payload {} on topic {}".format(payload, topic))
68
+
69
+ def main():
70
+ run_script(callback, broker="mqtt://127.0.0.1", topics=["my/awesome/topic1", "another/awesome/topic2"])
71
+
72
+ or in non blocking mode::
73
+
74
+ def main():
75
+ run_script(callback, broker="mqtt://127.0.0.1", topics=["my/awesome/topic1", "another/awesome/topic2"], blocking=False)
76
+ while True:
77
+ echo "Alive and kicking"
78
+ time.sleep(1)
79
+
80
+ Any extra keyword arguments passed to ``run_script`` are passed back to the
81
+ callback function when it's called::
82
+
83
+ from mqttwrapper import run_script
84
+
85
+ def callback(topic, payload, context, foo):
86
+ assert context == "hello"
87
+ assert foo == "bar"
88
+ print("Received payload {} on topic {}".format(payload, topic))
89
+
90
+ def main():
91
+ run_script(callback, broker="mqtt://127.0.0.1", topics=["my/awesome/topic1", "another/awesome/topic2"], context="hello", foo="bar")
92
+
93
+
94
+ If you don't need any context passed to your callback, just the topic and
95
+ payload of messages, you can run in 'importless' mode by creating a `callback`
96
+ module which has a `callback` function defined, then execute the
97
+ `mqttwrapper.run` module::
98
+
99
+ $ cat callback.py
100
+ def callback(topic, payload):
101
+ print(topic, payload)
102
+
103
+ $ python -m mqttwrapper.run
104
+
105
+ Retained messages
106
+ -----------------
107
+
108
+ You can ignore MQTT retained messages by passing ``ignore_retained=True`` when
109
+ calling ``run_script``. NB this is currently not supported by the hbmqtt
110
+ backend.
111
+
112
+ Publishing messages from the callback
113
+ -------------------------------------
114
+
115
+ Sometimes your callback function might want to publish its own MQTT messages,
116
+ perhaps in reply to or an acknowledgement of a received message. This is
117
+ possible by returning an iterable of ``(topic, payload)`` tuples from the callback,
118
+ e.g.::
119
+
120
+ def callback(topic, payload):
121
+ print("Received payload {} on topic {}".format(payload, topic))
122
+ return [
123
+ ("{}/ack".format(topic), payload)
124
+ ]
125
+
126
+
127
+ ``mqttwrapper`` will take care of publishing these messages to the broker.
128
+
129
+ These tuples can optionally take the form ``(topic, payload, True)`` if you want
130
+ the published message to be marked as retained.
131
+
132
+ Configuration
133
+ -------------
134
+
135
+ ``broker`` and ``topics`` can be omitted from the ``run_script`` call and
136
+ environment variables used instead:
137
+
138
+ - ``MQTT_BROKER``: MQTT broker to connect to, e.g. ``mqtt://127.0.0.1/``
139
+ - ``MQTT_TOPICS``: Comma-separated list of topics to subscribe to, e.g. ``my/topic1,my/topic2``
140
+
141
+ asyncio/hbmqtt
142
+ --------------
143
+
144
+ NB This feature is deprecated and will be removed in the future.
145
+
146
+ The asyncio-powered ``hbmqtt`` MQTT library can be used instead, if you like::
147
+
148
+ from mqttwrapper.hbmqtt_backend import run_script
149
+
150
+ async def callback(topic, payload):
151
+ print("Received payload {} on topic {}".format(payload, topic))
152
+
153
+
154
+ Note that your callback must be an awaitable in this case.
155
+
156
+ Your callback may require context arguments which themselves are async objects
157
+ or awaitables which poses a challenge: how to set these up outside of an asyncio
158
+ event loop before calling ``run_script``? In this case, you can pass a
159
+ ``context_callback`` awaitable as a kwarg to ``run_script``. This is run at the
160
+ start of the MQTT loop, and should return a dict which will be merged into the
161
+ kwargs which are passed to your callback. For example::
162
+
163
+ from mqttwrapper.hbmqtt_backend import run_script
164
+
165
+ async def setup_db():
166
+ return {
167
+ "query_db": query_db
168
+ }
169
+
170
+ async def query_db(value):
171
+ # pretend this is some slow DB query, for example.
172
+ await asyncio.sleep(3)
173
+ return value * 2
174
+
175
+ async def callback(topic, payload, query_db):
176
+ db_result = await query_db(int(payload))
177
+ print("Received payload {} on topic {}, db result: {}".format(payload, topic, db_result))
178
+
179
+ def main():
180
+ run_script(callback, context_callback=setup_db)
181
+
182
+
183
+ NB hbmqtt's reconnection handling does not resubscribe to topics upon
184
+ reconnection, and ``mqttwrapper`` does not yet work around this.
185
+
186
+ Examples
187
+ --------
188
+
189
+ - rxv2mqtt_
190
+ - tradfri-mqtt_ (uses asyncio)
191
+
192
+ .. _rxv2mqtt: https://github.com/davea/rxv2mqtt/blob/master/main.py
193
+ .. _tradfri-mqtt: https://github.com/davea/tradfri-mqtt/blob/master/main.py
@@ -0,0 +1,9 @@
1
+ mqttwrapper/__init__.py,sha256=Hm2aRNZ--y78D7ec7IGQCHk_Zf-VE1sF4p0FnRkHokw,37
2
+ mqttwrapper/hbmqtt_backend.py,sha256=mmDx2pT4fNrwcvu0CQ86X00kP26KyUAcHkm9h7YZz-4,1648
3
+ mqttwrapper/paho_backend.py,sha256=_QKY88_j_dW3rOlHI7Xe3IeTpiPiysFJXz0znhG68CU,2183
4
+ mqttwrapper/run.py,sha256=0ulC7Z6B-EDUDgFx3oYncRrmNlLB2y0o7j1EDmYPmiA,386
5
+ mqttwrapper-0.2.0.dist-info/licenses/LICENSE,sha256=uWqObWrb6MDtetS3K8TmVXuB0w0lSKXSA4ByGm6Fy00,1067
6
+ mqttwrapper-0.2.0.dist-info/METADATA,sha256=pRoUv_3e-Hz7YoBh2GnwbJDyAgdWX4oOe7V_0sBIigg,6095
7
+ mqttwrapper-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ mqttwrapper-0.2.0.dist-info/top_level.txt,sha256=Jqxgldb4d5BWTJir7PIzTe5lYpDHx_MinoDnnECwMCw,12
9
+ mqttwrapper-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Dave Arter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ mqttwrapper