1# Copyright (c) 2014 Roger Light <roger@atchoo.org>
2#
3# All rights reserved. This program and the accompanying materials
4# are made available under the terms of the Eclipse Public License v1.0
5# and Eclipse Distribution License v1.0 which accompany this distribution.
6#
7# The Eclipse Public License is available at
8#    http://www.eclipse.org/legal/epl-v10.html
9# and the Eclipse Distribution License is available at
10#   http://www.eclipse.org/org/documents/edl-v10.php.
11#
12# Contributors:
13#    Roger Light - initial API and implementation
14
15"""
16This module provides some helper functions to allow straightforward publishing
17of messages in a one-shot manner. In other words, they are useful for the
18situation where you have a single/multiple messages you want to publish to a
19broker, then disconnect and nothing else is required.
20"""
21from __future__ import absolute_import
22
23import collections
24
25from . import client as paho
26from .. import mqtt
27
28def _do_publish(client):
29    """Internal function"""
30
31    message = client._userdata.popleft()
32
33    if isinstance(message, dict):
34        client.publish(**message)
35    elif isinstance(message, (tuple, list)):
36        client.publish(*message)
37    else:
38        raise TypeError('message must be a dict, tuple, or list')
39
40
41def _on_connect(client, userdata, flags, rc):
42    """Internal callback"""
43    #pylint: disable=invalid-name, unused-argument
44
45    if rc == 0:
46        if len(userdata) > 0:
47            _do_publish(client)
48    else:
49        raise mqtt.MQTTException(paho.connack_string(rc))
50
51
52def _on_publish(client, userdata, mid):
53    """Internal callback"""
54    #pylint: disable=unused-argument
55
56    if len(userdata) == 0:
57        client.disconnect()
58    else:
59        _do_publish(client)
60
61
62def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60,
63             will=None, auth=None, tls=None, protocol=paho.MQTTv311,
64             transport="tcp", proxy_args=None):
65    """Publish multiple messages to a broker, then disconnect cleanly.
66
67    This function creates an MQTT client, connects to a broker and publishes a
68    list of messages. Once the messages have been delivered, it disconnects
69    cleanly from the broker.
70
71    msgs : a list of messages to publish. Each message is either a dict or a
72           tuple.
73
74           If a dict, only the topic must be present. Default values will be
75           used for any missing arguments. The dict must be of the form:
76
77           msg = {'topic':"<topic>", 'payload':"<payload>", 'qos':<qos>,
78           'retain':<retain>}
79           topic must be present and may not be empty.
80           If payload is "", None or not present then a zero length payload
81           will be published.
82           If qos is not present, the default of 0 is used.
83           If retain is not present, the default of False is used.
84
85           If a tuple, then it must be of the form:
86           ("<topic>", "<payload>", qos, retain)
87
88    hostname : a string containing the address of the broker to connect to.
89               Defaults to localhost.
90
91    port : the port to connect to the broker on. Defaults to 1883.
92
93    client_id : the MQTT client id to use. If "" or None, the Paho library will
94                generate a client id automatically.
95
96    keepalive : the keepalive timeout value for the client. Defaults to 60
97                seconds.
98
99    will : a dict containing will parameters for the client: will = {'topic':
100           "<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
101           Topic is required, all other parameters are optional and will
102           default to None, 0 and False respectively.
103           Defaults to None, which indicates no will should be used.
104
105    auth : a dict containing authentication parameters for the client:
106           auth = {'username':"<username>", 'password':"<password>"}
107           Username is required, password is optional and will default to None
108           if not provided.
109           Defaults to None, which indicates no authentication is to be used.
110
111    tls : a dict containing TLS configuration parameters for the client:
112          dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
113          'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
114          'ciphers':"<ciphers">, 'insecure':"<bool>"}
115          ca_certs is required, all other parameters are optional and will
116          default to None if not provided, which results in the client using
117          the default behaviour - see the paho.mqtt.client documentation.
118          Alternatively, tls input can be an SSLContext object, which will be
119          processed using the tls_set_context method.
120          Defaults to None, which indicates that TLS should not be used.
121
122    transport : set to "tcp" to use the default setting of transport which is
123          raw TCP. Set to "websockets" to use WebSockets as the transport.
124    proxy_args: a dictionary that will be given to the client.
125    """
126
127    if not isinstance(msgs, collections.Iterable):
128        raise TypeError('msgs must be an iterable')
129
130    client = paho.Client(client_id=client_id, userdata=collections.deque(msgs),
131                         protocol=protocol, transport=transport)
132
133    client.on_publish = _on_publish
134    client.on_connect = _on_connect
135
136    if proxy_args is not None:
137        client.proxy_set(**proxy_args)
138
139    if auth:
140        username = auth.get('username')
141        if username:
142            password = auth.get('password')
143            client.username_pw_set(username, password)
144        else:
145            raise KeyError("The 'username' key was not found, this is "
146                           "required for auth")
147
148    if will is not None:
149        client.will_set(**will)
150
151    if tls is not None:
152        if isinstance(tls, dict):
153            insecure = tls.pop('insecure', False)
154            client.tls_set(**tls)
155            if insecure:
156                # Must be set *after* the `client.tls_set()` call since it sets
157                # up the SSL context that `client.tls_insecure_set` alters.
158                client.tls_insecure_set(insecure)
159        else:
160            # Assume input is SSLContext object
161            client.tls_set_context(tls)
162
163    client.connect(hostname, port, keepalive)
164    client.loop_forever()
165
166
167def single(topic, payload=None, qos=0, retain=False, hostname="localhost",
168           port=1883, client_id="", keepalive=60, will=None, auth=None,
169           tls=None, protocol=paho.MQTTv311, transport="tcp", proxy_args=None):
170    """Publish a single message to a broker, then disconnect cleanly.
171
172    This function creates an MQTT client, connects to a broker and publishes a
173    single message. Once the message has been delivered, it disconnects cleanly
174    from the broker.
175
176    topic : the only required argument must be the topic string to which the
177            payload will be published.
178
179    payload : the payload to be published. If "" or None, a zero length payload
180              will be published.
181
182    qos : the qos to use when publishing,  default to 0.
183
184    retain : set the message to be retained (True) or not (False).
185
186    hostname : a string containing the address of the broker to connect to.
187               Defaults to localhost.
188
189    port : the port to connect to the broker on. Defaults to 1883.
190
191    client_id : the MQTT client id to use. If "" or None, the Paho library will
192                generate a client id automatically.
193
194    keepalive : the keepalive timeout value for the client. Defaults to 60
195                seconds.
196
197    will : a dict containing will parameters for the client: will = {'topic':
198           "<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
199           Topic is required, all other parameters are optional and will
200           default to None, 0 and False respectively.
201           Defaults to None, which indicates no will should be used.
202
203    auth : a dict containing authentication parameters for the client:
204           auth = {'username':"<username>", 'password':"<password>"}
205           Username is required, password is optional and will default to None
206           if not provided.
207           Defaults to None, which indicates no authentication is to be used.
208
209    tls : a dict containing TLS configuration parameters for the client:
210          dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
211          'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
212          'ciphers':"<ciphers">, 'insecure':"<bool>"}
213          ca_certs is required, all other parameters are optional and will
214          default to None if not provided, which results in the client using
215          the default behaviour - see the paho.mqtt.client documentation.
216          Defaults to None, which indicates that TLS should not be used.
217          Alternatively, tls input can be an SSLContext object, which will be
218          processed using the tls_set_context method.
219
220    transport : set to "tcp" to use the default setting of transport which is
221          raw TCP. Set to "websockets" to use WebSockets as the transport.
222    proxy_args: a dictionary that will be given to the client.
223    """
224
225    msg = {'topic':topic, 'payload':payload, 'qos':qos, 'retain':retain}
226
227    multiple([msg], hostname, port, client_id, keepalive, will, auth, tls,
228             protocol, transport, proxy_args)
229