mqtt 0.0.5 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README +12 -25
- data/lib/mqtt.rb +1 -1
- data/lib/mqtt/client.rb +76 -18
- data/lib/mqtt/version.rb +1 -1
- data/spec/mqtt_client_spec.rb +85 -32
- data/spec/mqtt_proxy_spec.rb +1 -1
- data/spec/mqtt_version_spec.rb +3 -3
- metadata +4 -4
data/README
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
ruby-mqtt
|
2
2
|
=========
|
3
3
|
|
4
|
-
Pure Ruby gem that implements the MQTT (Message Queue Telemetry Transport) protocol,
|
4
|
+
Pure Ruby gem that implements the MQTT (Message Queue Telemetry Transport) protocol,
|
5
|
+
a lightweight protocol for publish/subscribe messaging.
|
5
6
|
|
6
7
|
|
7
8
|
Installing
|
8
9
|
----------
|
9
10
|
|
10
|
-
You may get the latest stable version from
|
11
|
+
You may get the latest stable version from Rubygems:
|
11
12
|
|
12
13
|
$ gem install mqtt
|
13
14
|
|
15
|
+
|
14
16
|
Synopsis
|
15
17
|
--------
|
16
18
|
|
@@ -18,37 +20,22 @@ Synopsis
|
|
18
20
|
require 'mqtt'
|
19
21
|
|
20
22
|
# Publish example
|
21
|
-
|
22
|
-
|
23
|
-
c.publish('topic','message')
|
23
|
+
MQTT::Client.connect('test.mosquitto.org') do |c|
|
24
|
+
c.publish('topic', 'message')
|
24
25
|
end
|
25
26
|
|
26
27
|
# Subscribe example
|
27
|
-
|
28
|
-
|
29
|
-
client.subscribe('test')
|
30
|
-
loop do
|
31
|
-
topic,message = client.get
|
28
|
+
MQTT::Client.connect('test.mosquitto.org') do |c|
|
29
|
+
c.get('test') do |topic,message|
|
32
30
|
puts "#{topic}: #{message}"
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
34
|
|
37
|
-
|
38
|
-
|
35
|
+
Limitations
|
36
|
+
-----------
|
39
37
|
|
40
|
-
*
|
41
|
-
* Process acknowledgement packets / Implement QOS 1 in client
|
42
|
-
* More validations of data/parameters
|
43
|
-
* More error checking and exception throwing
|
44
|
-
- Check that packet data is valid - don't blindly read values
|
45
|
-
- Subscribe and Unsubscribe packets should always have QOS of 1
|
46
|
-
* More examples
|
47
|
-
* Integration tests
|
48
|
-
* Refactor to add callbacks that are called from seperate thread
|
49
|
-
* Implement QOS Level 2 in client
|
50
|
-
* Prevent proxy from connecting to itself
|
51
|
-
* Add support for binding socket to specific local address
|
38
|
+
* Only QOS 0 currently supported
|
52
39
|
|
53
40
|
|
54
41
|
Resources
|
@@ -56,7 +43,7 @@ Resources
|
|
56
43
|
|
57
44
|
* MQTT Homepage: http://www.mqtt.org/
|
58
45
|
* GitHub Project: http://github.com/njh/ruby-mqtt
|
59
|
-
* Documentation: http://
|
46
|
+
* API Documentation: http://rubydoc.info/gems/mqtt/frames
|
60
47
|
|
61
48
|
|
62
49
|
Contact
|
data/lib/mqtt.rb
CHANGED
data/lib/mqtt/client.rb
CHANGED
@@ -16,23 +16,67 @@ class MQTT::Client
|
|
16
16
|
# Timeout between select polls (in seconds)
|
17
17
|
SELECT_TIMEOUT = 0.5
|
18
18
|
|
19
|
+
# Default attribute values
|
20
|
+
ATTR_DEFAULTS = {
|
21
|
+
:remote_host => MQTT::DEFAULT_HOST,
|
22
|
+
:remote_port => MQTT::DEFAULT_PORT,
|
23
|
+
:keep_alive => 15,
|
24
|
+
:clean_session => true,
|
25
|
+
:client_id => nil,
|
26
|
+
:ack_timeout => 5,
|
27
|
+
:username => nil,
|
28
|
+
:password => nil
|
29
|
+
}
|
30
|
+
|
31
|
+
# Create and connect a new MQTT Client
|
32
|
+
# Accepts the same arguments as creating a new client.
|
33
|
+
# If a block is given, then it will be executed before disconnecting again.
|
34
|
+
#
|
35
|
+
# Example:
|
36
|
+
# MQTT::Client.connect('myserver.example.com') do |client|
|
37
|
+
# # do stuff here
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
def self.connect(*args, &block)
|
41
|
+
client = MQTT::Client.new(*args)
|
42
|
+
client.connect(&block)
|
43
|
+
return client
|
44
|
+
end
|
45
|
+
|
19
46
|
# Create a new MQTT Client instance
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
47
|
+
#
|
48
|
+
# Examples:
|
49
|
+
# client = MQTT::Client.new('myserver.example.com')
|
50
|
+
# client = MQTT::Client.new('myserver.example.com', 18830)
|
51
|
+
# client = MQTT::Client.new(:remote_host => 'myserver.example.com')
|
52
|
+
# client = MQTT::Client.new(:remote_host => 'myserver.example.com', :keep_alive => 30)
|
53
|
+
#
|
54
|
+
def initialize(*args)
|
55
|
+
if args.count == 0
|
56
|
+
args = {}
|
57
|
+
elsif args.count == 1 and args[0].is_a?(Hash)
|
58
|
+
args = args[0]
|
59
|
+
elsif args.count == 1
|
60
|
+
args = {:remote_host => args[0]}
|
61
|
+
elsif args.count == 2
|
62
|
+
args = {:remote_host => args[0], :remote_port => args[1]}
|
63
|
+
else
|
64
|
+
raise ArgumentError, "Unsupported number of arguments"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Merge arguments with default values for attributes
|
68
|
+
ATTR_DEFAULTS.merge(args).each_pair do |k,v|
|
69
|
+
instance_variable_set("@#{k}", v)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Initialise private instance variables
|
26
73
|
@message_id = 0
|
27
|
-
@ack_timeout = 5
|
28
74
|
@last_pingreq = Time.now
|
29
75
|
@last_pingresp = Time.now
|
30
76
|
@socket = nil
|
31
77
|
@read_queue = Queue.new
|
32
78
|
@read_thread = nil
|
33
79
|
@write_semaphore = Mutex.new
|
34
|
-
@username = nil
|
35
|
-
@password = nil
|
36
80
|
end
|
37
81
|
|
38
82
|
# Connect to the MQTT broker
|
@@ -40,7 +84,7 @@ class MQTT::Client
|
|
40
84
|
def connect(clientid=nil)
|
41
85
|
if !clientid.nil?
|
42
86
|
@client_id = clientid
|
43
|
-
elsif clientid.nil?
|
87
|
+
elsif @clientid.nil?
|
44
88
|
@client_id = random_letters(16)
|
45
89
|
@clean_session = true
|
46
90
|
end
|
@@ -140,17 +184,31 @@ class MQTT::Client
|
|
140
184
|
end
|
141
185
|
|
142
186
|
# Return the next message recieved from the MQTT broker.
|
143
|
-
#
|
187
|
+
# An optional topic can be given to subscribe to.
|
144
188
|
#
|
145
|
-
# The method returns the topic and message as an array:
|
189
|
+
# The method either returns the topic and message as an array:
|
146
190
|
# topic,message = client.get
|
147
191
|
#
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
192
|
+
# Or can be used with a block to keep processing messages:
|
193
|
+
# client.get('test') do |topic,payload|
|
194
|
+
# # Do stuff here
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
def get(topic=nil)
|
198
|
+
# Subscribe to a topic, if an argument is given
|
199
|
+
subscribe(topic) unless topic.nil?
|
200
|
+
|
201
|
+
if block_given?
|
202
|
+
# Loop forever!
|
203
|
+
loop do
|
204
|
+
packet = @read_queue.pop
|
205
|
+
yield(packet.topic, packet.payload)
|
206
|
+
end
|
207
|
+
else
|
208
|
+
# Wait for one packet to be available
|
209
|
+
packet = @read_queue.pop
|
210
|
+
return packet.topic, packet.payload
|
211
|
+
end
|
154
212
|
end
|
155
213
|
|
156
214
|
# Send a unsubscribe message for one or more topics on the MQTT broker
|
data/lib/mqtt/version.rb
CHANGED
data/spec/mqtt_client_spec.rb
CHANGED
@@ -9,45 +9,98 @@ describe MQTT::Client do
|
|
9
9
|
@client = MQTT::Client.new
|
10
10
|
@socket = StringIO.new
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
|
+
describe "initializing a client" do
|
14
|
+
it "with no arguments, it should use the defaults" do
|
15
|
+
@client = MQTT::Client.new
|
16
|
+
@client.remote_host.should == 'localhost'
|
17
|
+
@client.remote_port.should == 1883
|
18
|
+
@client.keep_alive.should == 15
|
19
|
+
end
|
20
|
+
|
21
|
+
it "with a single string argument, it should use it has the host" do
|
22
|
+
@client = MQTT::Client.new('otherhost.mqtt.org')
|
23
|
+
@client.remote_host.should == 'otherhost.mqtt.org'
|
24
|
+
@client.remote_port.should == 1883
|
25
|
+
@client.keep_alive.should == 15
|
26
|
+
end
|
27
|
+
|
28
|
+
it "with two arguments, it should use it as the host and port" do
|
29
|
+
@client = MQTT::Client.new('otherhost.mqtt.org', 1000)
|
30
|
+
@client.remote_host.should == 'otherhost.mqtt.org'
|
31
|
+
@client.remote_port.should == 1000
|
32
|
+
@client.keep_alive.should == 15
|
33
|
+
end
|
34
|
+
|
35
|
+
it "with names arguments, it should use those as arguments" do
|
36
|
+
@client = MQTT::Client.new(:remote_host => 'otherhost.mqtt.org', :remote_port => 1000)
|
37
|
+
@client.remote_host.should == 'otherhost.mqtt.org'
|
38
|
+
@client.remote_port.should == 1000
|
39
|
+
@client.keep_alive.should == 15
|
40
|
+
end
|
41
|
+
|
42
|
+
it "with a hash, it should use those as arguments" do
|
43
|
+
@client = MQTT::Client.new({:remote_host => 'otherhost.mqtt.org', :remote_port => 1000})
|
44
|
+
@client.remote_host.should == 'otherhost.mqtt.org'
|
45
|
+
@client.remote_port.should == 1000
|
46
|
+
@client.keep_alive.should == 15
|
47
|
+
end
|
48
|
+
|
49
|
+
it "with a hash containing just a keep alive setting" do
|
50
|
+
@client = MQTT::Client.new(:keep_alive => 60)
|
51
|
+
@client.remote_host.should == 'localhost'
|
52
|
+
@client.remote_port.should == 1883
|
53
|
+
@client.keep_alive.should == 60
|
54
|
+
end
|
55
|
+
|
56
|
+
it "with three arguments" do
|
57
|
+
lambda {
|
58
|
+
@client = MQTT::Client.new(1, 2, 3)
|
59
|
+
}.should raise_error(
|
60
|
+
'Unsupported number of arguments'
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
13
66
|
describe "when calling the 'connect' method" do
|
14
67
|
before(:each) do
|
15
68
|
TCPSocket.stubs(:new).returns(@socket)
|
16
69
|
Thread.stubs(:new)
|
17
70
|
@client.stubs(:receive_connack)
|
18
71
|
end
|
19
|
-
|
72
|
+
|
20
73
|
it "should create a TCP Socket if not connected" do
|
21
74
|
TCPSocket.expects(:new).once.returns(@socket)
|
22
75
|
@client.connect('myclient')
|
23
76
|
end
|
24
|
-
|
77
|
+
|
25
78
|
it "should not create a new TCP Socket if connected" do
|
26
79
|
@client.stubs(:connected?).returns(true)
|
27
80
|
TCPSocket.expects(:new).never
|
28
81
|
@client.connect('myclient')
|
29
82
|
end
|
30
|
-
|
83
|
+
|
31
84
|
it "should start the reader thread if not connected" do
|
32
85
|
Thread.expects(:new).once
|
33
86
|
@client.connect('myclient')
|
34
87
|
end
|
35
|
-
|
88
|
+
|
36
89
|
it "should write a valid CONNECT packet to the socket if not connected" do
|
37
90
|
@client.connect('myclient')
|
38
|
-
@socket.string.should == "\020\026\x00\x06MQIsdp\x03\x02\x00\
|
91
|
+
@socket.string.should == "\020\026\x00\x06MQIsdp\x03\x02\x00\x0f\x00\x08myclient"
|
39
92
|
end
|
40
|
-
|
93
|
+
|
41
94
|
it "should try and read an acknowledgement packet to the socket if not connected" do
|
42
95
|
@client.expects(:receive_connack).once
|
43
96
|
@client.connect('myclient')
|
44
97
|
end
|
45
|
-
|
98
|
+
|
46
99
|
it "should disconnect after connecting, if a block is given" do
|
47
100
|
@client.expects(:disconnect).once
|
48
101
|
@client.connect('myclient') { nil }
|
49
102
|
end
|
50
|
-
|
103
|
+
|
51
104
|
it "should not disconnect after connecting, if no block is given" do
|
52
105
|
@client.expects(:disconnect).never
|
53
106
|
@client.connect('myclient')
|
@@ -60,8 +113,8 @@ describe MQTT::Client do
|
|
60
113
|
@socket.string.should ==
|
61
114
|
"\x10\x2A"+
|
62
115
|
"\x00\x06MQIsdp"+
|
63
|
-
"\x03\xC2\x00\
|
64
|
-
"\x08myclient"+
|
116
|
+
"\x03\xC2\x00\x0f"+
|
117
|
+
"\x00\x08myclient"+
|
65
118
|
"\x00\x08username"+
|
66
119
|
"\x00\x08password"
|
67
120
|
end
|
@@ -79,74 +132,74 @@ describe MQTT::Client do
|
|
79
132
|
@client.instance_variable_set(:@socket, @socket)
|
80
133
|
IO.stubs(:select).returns([[@socket], [], []])
|
81
134
|
end
|
82
|
-
|
135
|
+
|
83
136
|
it "should not throw an exception for a successful CONNACK packet" do
|
84
137
|
@socket.write("\x20\x02\x00\x00")
|
85
138
|
@socket.rewind
|
86
139
|
lambda { @client.send(:receive_connack) }.should_not raise_error
|
87
140
|
end
|
88
|
-
|
141
|
+
|
89
142
|
it "should throw an exception if the packet type isn't CONNACK" do
|
90
143
|
@socket.write("\xD0\x00")
|
91
144
|
@socket.rewind
|
92
145
|
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException)
|
93
146
|
end
|
94
|
-
|
147
|
+
|
95
148
|
it "should throw an exception if the CONNACK packet return code is 'unacceptable protocol version'" do
|
96
149
|
@socket.write("\x20\x02\x00\x01")
|
97
150
|
@socket.rewind
|
98
151
|
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /unacceptable protocol version/i)
|
99
152
|
end
|
100
|
-
|
153
|
+
|
101
154
|
it "should throw an exception if the CONNACK packet return code is 'client identifier rejected'" do
|
102
155
|
@socket.write("\x20\x02\x00\x02")
|
103
156
|
@socket.rewind
|
104
157
|
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /client identifier rejected/i)
|
105
158
|
end
|
106
|
-
|
159
|
+
|
107
160
|
it "should throw an exception if the CONNACK packet return code is 'broker unavailable'" do
|
108
161
|
@socket.write("\x20\x02\x00\x03")
|
109
162
|
@socket.rewind
|
110
163
|
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /broker unavailable/i)
|
111
164
|
end
|
112
|
-
|
165
|
+
|
113
166
|
it "should throw an exception if the CONNACK packet return code is an unknown" do
|
114
167
|
@socket.write("\x20\x02\x00\xAA")
|
115
168
|
@socket.rewind
|
116
169
|
lambda { @client.send(:receive_connack) }.should raise_error(MQTT::ProtocolException, /connection refused/i)
|
117
170
|
end
|
118
171
|
end
|
119
|
-
|
172
|
+
|
120
173
|
describe "when calling the 'disconnect' method" do
|
121
174
|
before(:each) do
|
122
175
|
@client.instance_variable_set(:@socket, @socket)
|
123
176
|
@client.instance_variable_set(:@read_thread, stub_everything('Read Thread'))
|
124
177
|
end
|
125
|
-
|
178
|
+
|
126
179
|
it "should not do anything if the socket is already disconnected" do
|
127
180
|
@client.stubs(:connected?).returns(false)
|
128
181
|
@client.disconnect(true)
|
129
182
|
@socket.string.should == ""
|
130
183
|
end
|
131
|
-
|
184
|
+
|
132
185
|
it "should write a valid DISCONNECT packet to the socket if connected and the send_msg=true an" do
|
133
186
|
@client.stubs(:connected?).returns(true)
|
134
187
|
@client.disconnect(true)
|
135
188
|
@socket.string.should == "\xE0\x00"
|
136
189
|
end
|
137
|
-
|
190
|
+
|
138
191
|
it "should not write anything to the socket if the send_msg=false" do
|
139
192
|
@client.stubs(:connected?).returns(true)
|
140
193
|
@client.disconnect(false)
|
141
194
|
@socket.string.should be_empty
|
142
195
|
end
|
143
|
-
|
196
|
+
|
144
197
|
it "should call the close method on the socket" do
|
145
198
|
@socket.expects(:close)
|
146
199
|
@client.disconnect
|
147
200
|
end
|
148
201
|
end
|
149
|
-
|
202
|
+
|
150
203
|
describe "when calling the 'ping' method" do
|
151
204
|
before(:each) do
|
152
205
|
@client.instance_variable_set(:@socket, @socket)
|
@@ -163,7 +216,7 @@ describe MQTT::Client do
|
|
163
216
|
@client.instance_variable_get(:@last_pingreq).should_not == 0
|
164
217
|
end
|
165
218
|
end
|
166
|
-
|
219
|
+
|
167
220
|
describe "when calling the 'publish' method" do
|
168
221
|
before(:each) do
|
169
222
|
@client.instance_variable_set(:@socket, @socket)
|
@@ -173,17 +226,17 @@ describe MQTT::Client do
|
|
173
226
|
@client.publish('topic','payload', false, 0)
|
174
227
|
@socket.string.should == "\x30\x0e\x00\x05topicpayload"
|
175
228
|
end
|
176
|
-
|
229
|
+
|
177
230
|
it "should write a valid PUBLISH packet to the socket with the retain flag set" do
|
178
231
|
@client.publish('topic','payload', true, 0)
|
179
232
|
@socket.string.should == "\x31\x0e\x00\x05topicpayload"
|
180
233
|
end
|
181
|
-
|
234
|
+
|
182
235
|
it "should write a valid PUBLISH packet to the socket with the QOS set to 1" do
|
183
236
|
@client.publish('topic','payload', false, 1)
|
184
237
|
@socket.string.should == "\x32\x10\x00\x05topic\x00\x01payload"
|
185
238
|
end
|
186
|
-
|
239
|
+
|
187
240
|
it "should write a valid PUBLISH packet to the socket with the QOS set to 2" do
|
188
241
|
@client.publish('topic','payload', false, 2)
|
189
242
|
@socket.string.should == "\x34\x10\x00\x05topic\x00\x01payload"
|
@@ -250,13 +303,13 @@ describe MQTT::Client do
|
|
250
303
|
@client.unsubscribe('a/b')
|
251
304
|
@socket.string.should == "\xa2\x07\x00\x01\x00\x03a/b"
|
252
305
|
end
|
253
|
-
|
306
|
+
|
254
307
|
it "should write a valid UNSUBSCRIBE packet to the socket if given a two topic Strings" do
|
255
308
|
@client.unsubscribe('a/b','c/d')
|
256
309
|
@socket.string.should == "\xa2\x0c\x00\x01\x00\x03a/b\x00\x03c/d"
|
257
310
|
end
|
258
311
|
end
|
259
|
-
|
312
|
+
|
260
313
|
describe "when calling the 'receive_packet' method" do
|
261
314
|
before(:each) do
|
262
315
|
@client.instance_variable_set(:@socket, @socket)
|
@@ -278,7 +331,7 @@ describe MQTT::Client do
|
|
278
331
|
@client.send(:receive_packet)
|
279
332
|
@read_queue.size.should == 0
|
280
333
|
end
|
281
|
-
|
334
|
+
|
282
335
|
it "should send a ping packet if one is due" do
|
283
336
|
IO.expects(:select).returns(nil)
|
284
337
|
@client.instance_variable_set(:@last_pingreq, Time.at(0))
|
@@ -297,7 +350,7 @@ describe MQTT::Client do
|
|
297
350
|
MQTT::Packet.stubs(:read).raises(MQTT::Exception)
|
298
351
|
@client.send(:receive_packet)
|
299
352
|
end
|
300
|
-
|
353
|
+
|
301
354
|
end
|
302
|
-
|
355
|
+
|
303
356
|
end
|
data/spec/mqtt_proxy_spec.rb
CHANGED
data/spec/mqtt_version_spec.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mqtt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 17
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 7
|
10
|
+
version: 0.0.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Nicholas J Humfrey
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-01-
|
18
|
+
date: 2012-01-19 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|