paho-mqtt 1.0.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/lib/paho-mqtt.rb +72 -1
- data/lib/paho_mqtt/client.rb +182 -708
- data/lib/paho_mqtt/connection_helper.rb +167 -0
- data/lib/paho_mqtt/handler.rb +271 -0
- data/lib/paho_mqtt/packet.rb +14 -0
- data/lib/paho_mqtt/packet/base.rb +17 -0
- data/lib/paho_mqtt/packet/connack.rb +19 -3
- data/lib/paho_mqtt/packet/connect.rb +50 -21
- data/lib/paho_mqtt/packet/disconnect.rb +18 -1
- data/lib/paho_mqtt/packet/pingreq.rb +17 -0
- data/lib/paho_mqtt/packet/pingresp.rb +17 -0
- data/lib/paho_mqtt/packet/puback.rb +17 -1
- data/lib/paho_mqtt/packet/pubcomp.rb +17 -0
- data/lib/paho_mqtt/packet/publish.rb +17 -0
- data/lib/paho_mqtt/packet/pubrec.rb +17 -0
- data/lib/paho_mqtt/packet/pubrel.rb +17 -0
- data/lib/paho_mqtt/packet/suback.rb +17 -0
- data/lib/paho_mqtt/packet/subscribe.rb +17 -0
- data/lib/paho_mqtt/packet/unsuback.rb +17 -0
- data/lib/paho_mqtt/packet/unsubscribe.rb +17 -0
- data/lib/paho_mqtt/publisher.rb +180 -0
- data/lib/paho_mqtt/sender.rb +90 -0
- data/lib/paho_mqtt/ssl_helper.rb +42 -0
- data/lib/paho_mqtt/subscriber.rb +195 -0
- data/lib/paho_mqtt/version.rb +1 -1
- metadata +8 -3
- data/LICENSE.txt +0 -21
@@ -0,0 +1,167 @@
|
|
1
|
+
# Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
|
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
|
+
# https://eclipse.org/org/documents/epl-v10.php.
|
9
|
+
# and the Eclipse Distribution License is available at
|
10
|
+
# https://eclipse.org/org/documents/edl-v10.php.
|
11
|
+
#
|
12
|
+
# Contributors:
|
13
|
+
# Pierre Goudet - initial committer
|
14
|
+
|
15
|
+
require 'socket'
|
16
|
+
|
17
|
+
module PahoMqtt
|
18
|
+
class ConnectionHelper
|
19
|
+
|
20
|
+
attr_accessor :sender
|
21
|
+
|
22
|
+
def initialize(host, port, ssl, ssl_context, ack_timeout)
|
23
|
+
@cs = MQTT_CS_DISCONNECT
|
24
|
+
@socket = nil
|
25
|
+
@host = host
|
26
|
+
@port = port
|
27
|
+
@ssl = ssl
|
28
|
+
@ssl_context = ssl_context
|
29
|
+
@ack_timeout = ack_timeout
|
30
|
+
@sender = Sender.new(ack_timeout)
|
31
|
+
end
|
32
|
+
|
33
|
+
def handler=(handler)
|
34
|
+
@handler = handler
|
35
|
+
end
|
36
|
+
|
37
|
+
def do_connect(reconnection=false)
|
38
|
+
@handler.socket = @socket
|
39
|
+
# Waiting a Connack packet for "ack_timeout" second from the remote
|
40
|
+
connect_timeout = Time.now + @ack_timeout
|
41
|
+
while (Time.now <= connect_timeout) && (!is_connected?) do
|
42
|
+
@cs = @handler.receive_packet
|
43
|
+
sleep 0.0001
|
44
|
+
end
|
45
|
+
unless is_connected?
|
46
|
+
PahoMqtt.logger.warn("Connection failed. Couldn't recieve a Connack packet from: #{@host}, socket is \"#{@socket}\".") if PahoMqtt.logger?
|
47
|
+
raise Exception.new("Connection failed. Check log for more details.") unless reconnection
|
48
|
+
end
|
49
|
+
@cs
|
50
|
+
end
|
51
|
+
|
52
|
+
def is_connected?
|
53
|
+
@cs == MQTT_CS_CONNECTED
|
54
|
+
end
|
55
|
+
|
56
|
+
def do_disconnect(publisher, explicit, mqtt_thread)
|
57
|
+
PahoMqtt.logger.debug("Disconnecting from #{@host}") if PahoMqtt.logger?
|
58
|
+
if explicit
|
59
|
+
explicit_disconnect(publisher, mqtt_thread)
|
60
|
+
end
|
61
|
+
@socket.close unless @socket.nil? || @socket.closed?
|
62
|
+
@socket = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def explicit_disconnect(publisher, mqtt_thread)
|
66
|
+
@sender.flush_waiting_packet
|
67
|
+
send_disconnect
|
68
|
+
mqtt_thread.kill if mqtt_thread && mqtt_thread.alive?
|
69
|
+
publisher.flush_publisher unless publisher.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
def setup_connection
|
73
|
+
clean_start(@host, @port)
|
74
|
+
config_socket
|
75
|
+
unless @socket.nil?
|
76
|
+
@sender.socket = @socket
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def config_socket
|
81
|
+
PahoMqtt.logger.debug("Atempt to connect to host: #{@host}") if PahoMqtt.logger?
|
82
|
+
begin
|
83
|
+
tcp_socket = TCPSocket.new(@host, @port)
|
84
|
+
rescue StandardError
|
85
|
+
PahoMqtt.logger.warn("Could not open a socket with #{@host} on port #{@port}") if PahoMqtt.logger?
|
86
|
+
end
|
87
|
+
if @ssl
|
88
|
+
encrypted_socket(tcp_socket, @ssl_context)
|
89
|
+
else
|
90
|
+
@socket = tcp_socket
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def encrypted_socket(tcp_socket, ssl_context)
|
95
|
+
unless ssl_context.nil?
|
96
|
+
@socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
|
97
|
+
@socket.sync_close = true
|
98
|
+
@socket.connect
|
99
|
+
else
|
100
|
+
PahoMqtt.logger.error("The ssl context was found as nil while the socket's opening.") if PahoMqtt.logger?
|
101
|
+
raise Exception
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def clean_start(host, port)
|
106
|
+
self.host = host
|
107
|
+
self.port = port
|
108
|
+
unless @socket.nil?
|
109
|
+
@socket.close unless @socket.closed?
|
110
|
+
@socket = nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def host=(host)
|
115
|
+
if host.nil? || host == ""
|
116
|
+
PahoMqtt.logger.error("The host was found as nil while the connection setup.") if PahoMqtt.logger?
|
117
|
+
raise ArgumentError
|
118
|
+
else
|
119
|
+
@host = host
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def port=(port)
|
124
|
+
if port.to_i <= 0
|
125
|
+
PahoMqtt.logger.error("The port value is invalid (<= 0). Could not setup the connection.") if PahoMqtt.logger?
|
126
|
+
raise ArgumentError
|
127
|
+
else
|
128
|
+
@port = port
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def send_connect(session_params)
|
133
|
+
setup_connection
|
134
|
+
packet = PahoMqtt::Packet::Connect.new(session_params)
|
135
|
+
@handler.clean_session = session_params[:clean_session]
|
136
|
+
@sender.send_packet(packet)
|
137
|
+
MQTT_ERR_SUCCESS
|
138
|
+
end
|
139
|
+
|
140
|
+
def send_disconnect
|
141
|
+
packet = PahoMqtt::Packet::Disconnect.new
|
142
|
+
@sender.send_packet(packet)
|
143
|
+
MQTT_ERR_SUCCESS
|
144
|
+
end
|
145
|
+
|
146
|
+
def send_pingreq
|
147
|
+
packet = PahoMqtt::Packet::Pingreq.new
|
148
|
+
@sender.send_packet(packet)
|
149
|
+
MQTT_ERR_SUCCESS
|
150
|
+
end
|
151
|
+
|
152
|
+
def check_keep_alive(persistent, last_ping_resp, keep_alive)
|
153
|
+
now = Time.now
|
154
|
+
timeout_req = (@sender.last_ping_req + (keep_alive * 0.7).ceil)
|
155
|
+
if timeout_req <= now && persistent
|
156
|
+
PahoMqtt.logger.debug("Checking if server is still alive.") if PahoMqtt.logger?
|
157
|
+
send_pingreq
|
158
|
+
end
|
159
|
+
timeout_resp = last_ping_resp + (keep_alive * 1.1).ceil
|
160
|
+
if timeout_resp <= now
|
161
|
+
PahoMqtt.logger.debug("No activity period over timeout, disconnecting from #{@host}") if PahoMqtt.logger?
|
162
|
+
@cs = MQTT_CS_DISCONNECT
|
163
|
+
end
|
164
|
+
@cs
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
|
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
|
+
# https://eclipse.org/org/documents/epl-v10.php.
|
9
|
+
# and the Eclipse Distribution License is available at
|
10
|
+
# https://eclipse.org/org/documents/edl-v10.php.
|
11
|
+
#
|
12
|
+
# Contributors:
|
13
|
+
# Pierre Goudet - initial committer
|
14
|
+
|
15
|
+
module PahoMqtt
|
16
|
+
class Handler
|
17
|
+
|
18
|
+
attr_reader :registered_callback
|
19
|
+
attr_accessor :last_ping_resp
|
20
|
+
attr_accessor :clean_session
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@registered_callback = []
|
24
|
+
@last_ping_resp = -1
|
25
|
+
@publisher = nil
|
26
|
+
@subscriber = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def config_pubsub(publisher, subscriber)
|
30
|
+
@publisher = publisher
|
31
|
+
@subscriber = subscriber
|
32
|
+
end
|
33
|
+
|
34
|
+
def socket=(socket)
|
35
|
+
@socket = socket
|
36
|
+
end
|
37
|
+
|
38
|
+
def receive_packet
|
39
|
+
result = IO.select([@socket], [], [], SELECT_TIMEOUT) unless @socket.nil? || @socket.closed?
|
40
|
+
unless result.nil?
|
41
|
+
packet = PahoMqtt::Packet::Base.read(@socket)
|
42
|
+
unless packet.nil?
|
43
|
+
if packet.is_a?(PahoMqtt::Packet::Connack)
|
44
|
+
@last_ping_resp = Time.now
|
45
|
+
handle_connack(packet)
|
46
|
+
else
|
47
|
+
handle_packet(packet)
|
48
|
+
@last_ping_resp = Time.now
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle_packet(packet)
|
55
|
+
PahoMqtt.logger.info("New packet #{packet.class} recieved.") if PahoMqtt.logger?
|
56
|
+
type = packet_type(packet)
|
57
|
+
self.send("handle_#{type}", packet)
|
58
|
+
end
|
59
|
+
|
60
|
+
def register_topic_callback(topic, callback, &block)
|
61
|
+
if topic.nil?
|
62
|
+
PahoMqtt.logger.error("The topics where the callback is trying to be registered have been found nil.") if PahoMqtt.logger?
|
63
|
+
raise ArgumentError
|
64
|
+
end
|
65
|
+
clear_topic_callback(topic)
|
66
|
+
if block_given?
|
67
|
+
@registered_callback.push([topic, block])
|
68
|
+
elsif !(callback.nil?) && callback.is_a?(Proc)
|
69
|
+
@registered_callback.push([topic, callback])
|
70
|
+
end
|
71
|
+
MQTT_ERR_SUCCESS
|
72
|
+
end
|
73
|
+
|
74
|
+
def clear_topic_callback(topic)
|
75
|
+
if topic.nil?
|
76
|
+
PahoMqtt.logger.error("The topics where the callback is trying to be unregistered have been found nil.") if PahoMqtt.logger?
|
77
|
+
raise ArgumentError
|
78
|
+
end
|
79
|
+
@registered_callback.delete_if {|pair| pair.first == topic}
|
80
|
+
MQTT_ERR_SUCCESS
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_connack(packet)
|
84
|
+
if packet.return_code == 0x00
|
85
|
+
PahoMqtt.logger.debug("Connack receive and connection accepted.") if PahoMqtt.logger?
|
86
|
+
handle_connack_accepted(packet.session_present)
|
87
|
+
else
|
88
|
+
handle_connack_error(packet.return_code)
|
89
|
+
end
|
90
|
+
@on_connack.call(packet) unless @on_connack.nil?
|
91
|
+
MQTT_CS_CONNECTED
|
92
|
+
end
|
93
|
+
|
94
|
+
def handle_connack_accepted(session_flag)
|
95
|
+
clean_session?(session_flag)
|
96
|
+
new_session?(session_flag)
|
97
|
+
old_session?(session_flag)
|
98
|
+
end
|
99
|
+
|
100
|
+
def new_session?(session_flag)
|
101
|
+
if !@clean_session && !session_flag
|
102
|
+
PahoMqtt.logger.debug("New session created for the client") if PahoMqtt.logger?
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def clean_session?(session_flag)
|
107
|
+
if @clean_session && !session_flag
|
108
|
+
PahoMqtt.logger.debug("No previous session found by server, starting a new one.") if PahoMqtt.logger?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def old_session?(session_flag)
|
113
|
+
if !@clean_session && session_flag
|
114
|
+
PahoMqtt.logger.debug("Previous session restored by the server.") if PahoMqtt.logger?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def handle_pingresp(_packet)
|
119
|
+
@last_ping_resp = Time.now
|
120
|
+
end
|
121
|
+
|
122
|
+
def handle_suback(packet)
|
123
|
+
max_qos = packet.return_codes
|
124
|
+
id = packet.id
|
125
|
+
topics = []
|
126
|
+
if @subscriber.add_subscription(max_qos, id, topics) == MQTT_ERR_SUCCESS
|
127
|
+
@on_suback.call(topics) unless @on_suback.nil?
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def handle_unsuback(packet)
|
132
|
+
id = packet.id
|
133
|
+
topics = []
|
134
|
+
if @subscriber.remove_subscription(id, topics) == MQTT_ERR_SUCCESS
|
135
|
+
@on_unsuback.call(topics) unless @on_unsuback.nil?
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def handle_publish(packet)
|
140
|
+
id = packet.id
|
141
|
+
qos = packet.qos
|
142
|
+
if @publisher.do_publish(qos, id) == MQTT_ERR_SUCCESS
|
143
|
+
@on_message.call(packet) unless @on_message.nil?
|
144
|
+
@registered_callback.assoc(packet.topic).last.call(packet) if @registered_callback.any? { |pair| pair.first == packet.topic}
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def handle_puback(packet)
|
149
|
+
id = packet.id
|
150
|
+
if @publisher.do_puback(id) == MQTT_ERR_SUCCESS
|
151
|
+
@on_puback.call(packet) unless @on_puback.nil?
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def handle_pubrec(packet)
|
156
|
+
id = packet.id
|
157
|
+
if @publisher.do_pubrec(id) == MQTT_ERR_SUCCESS
|
158
|
+
@on_pubrec.call(packet) unless @on_pubrec.nil?
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def handle_pubrel(packet)
|
163
|
+
id = packet.id
|
164
|
+
if @publisher.do_pubrel(id) == MQTT_ERR_SUCCESS
|
165
|
+
@on_pubrel.call(packet) unless @on_pubrel.nil?
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def handle_pubcomp(packet)
|
170
|
+
id = packet.id
|
171
|
+
if @publisher.do_pubcomp(id) == MQTT_ERR_SUCCESS
|
172
|
+
@on_pubcomp.call(packet) unless @on_pubcomp.nil?
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def handle_connack_error(return_code)
|
177
|
+
if return_code == 0x01
|
178
|
+
raise LowVersionException
|
179
|
+
elsif CONNACK_ERROR_MESSAGE.has_key(return_code.to_sym)
|
180
|
+
PahoMqtt.logger.warm(CONNACK_ERRO_MESSAGE[return_code])
|
181
|
+
MQTT_CS_DISCONNECTED
|
182
|
+
else
|
183
|
+
PahoMqtt.logger("Unknown return code for CONNACK packet: #{return_code}")
|
184
|
+
raise PacketException
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def on_connack(&block)
|
189
|
+
@on_connack = block if block_given?
|
190
|
+
@on_connack
|
191
|
+
end
|
192
|
+
|
193
|
+
def on_suback(&block)
|
194
|
+
@on_suback = block if block_given?
|
195
|
+
@on_suback
|
196
|
+
end
|
197
|
+
|
198
|
+
def on_unsuback(&block)
|
199
|
+
@on_unsuback = block if block_given?
|
200
|
+
@on_unsuback
|
201
|
+
end
|
202
|
+
|
203
|
+
def on_puback(&block)
|
204
|
+
@on_puback = block if block_given?
|
205
|
+
@on_puback
|
206
|
+
end
|
207
|
+
|
208
|
+
def on_pubrec(&block)
|
209
|
+
@on_pubrec = block if block_given?
|
210
|
+
@on_pubrec
|
211
|
+
end
|
212
|
+
|
213
|
+
def on_pubrel(&block)
|
214
|
+
@on_pubrel = block if block_given?
|
215
|
+
@on_pubrel
|
216
|
+
end
|
217
|
+
|
218
|
+
def on_pubcomp(&block)
|
219
|
+
@on_pubcomp = block if block_given?
|
220
|
+
@on_pubcomp
|
221
|
+
end
|
222
|
+
|
223
|
+
def on_message(&block)
|
224
|
+
@on_message = block if block_given?
|
225
|
+
@on_message
|
226
|
+
end
|
227
|
+
|
228
|
+
def on_connack=(callback)
|
229
|
+
@on_connack = callback if callback.is_a?(Proc)
|
230
|
+
end
|
231
|
+
|
232
|
+
def on_suback=(callback)
|
233
|
+
@on_suback = callback if callback.is_a?(Proc)
|
234
|
+
end
|
235
|
+
|
236
|
+
def on_unsuback=(callback)
|
237
|
+
@on_unsuback = callback if callback.is_a?(Proc)
|
238
|
+
end
|
239
|
+
|
240
|
+
def on_puback=(callback)
|
241
|
+
@on_puback = callback if callback.is_a?(Proc)
|
242
|
+
end
|
243
|
+
|
244
|
+
def on_pubrec=(callback)
|
245
|
+
@on_pubrec = callback if callback.is_a?(Proc)
|
246
|
+
end
|
247
|
+
|
248
|
+
def on_pubrel=(callback)
|
249
|
+
@on_pubrel = callback if callback.is_a?(Proc)
|
250
|
+
end
|
251
|
+
|
252
|
+
def on_pubcomp=(callback)
|
253
|
+
@on_pubcomp = callback if callback.is_a?(Proc)
|
254
|
+
end
|
255
|
+
|
256
|
+
def on_message=(callback)
|
257
|
+
@on_message = callback if callback.is_a?(Proc)
|
258
|
+
end
|
259
|
+
|
260
|
+
def packet_type(packet)
|
261
|
+
type = packet.class
|
262
|
+
if PahoMqtt::PACKET_TYPES[3..13].include?(type)
|
263
|
+
type.to_s.split('::').last.downcase
|
264
|
+
else
|
265
|
+
puts "Packet: #{packet.inspect}"
|
266
|
+
PahoMqtt.logger.error("Received an unexpeceted packet: #{packet}") if PahoMqtt.logger?
|
267
|
+
raise PacketException
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
data/lib/paho_mqtt/packet.rb
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
# encoding: BINARY
|
2
|
+
# Copyright (c) 2016-2017 Pierre Goudet <p-goudet@ruby-dev.jp>
|
3
|
+
#
|
4
|
+
# All rights reserved. This program and the accompanying materials
|
5
|
+
# are made available under the terms of the Eclipse Public License v1.0
|
6
|
+
# and Eclipse Distribution License v1.0 which accompany this distribution.
|
7
|
+
#
|
8
|
+
# The Eclipse Public License is available at
|
9
|
+
# https://eclipse.org/org/documents/epl-v10.php.
|
10
|
+
# and the Eclipse Distribution License is available at
|
11
|
+
# https://eclipse.org/org/documents/edl-v10.php.
|
12
|
+
#
|
13
|
+
# Contributors:
|
14
|
+
# Pierre Goudet - initial committer
|
15
|
+
|
2
16
|
require "paho_mqtt/packet/base"
|
3
17
|
require "paho_mqtt/packet/connect"
|
4
18
|
require "paho_mqtt/packet/connack"
|