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 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, a lightweight protocol for publish/subscribe messaging.
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 Rubyforge. Source gems are also available.
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
- mqtt = MQTT::Client.new('mqtt.example.com')
22
- mqtt.connect do |c|
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
- mqtt = MQTT::Client.new('mqtt.example.com')
28
- client.connect do
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
- TODO
38
- ----
35
+ Limitations
36
+ -----------
39
37
 
40
- * Implement Will and Testament
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://rdoc.info/github/njh/ruby-mqtt/master/frames
46
+ * API Documentation: http://rubydoc.info/gems/mqtt/frames
60
47
 
61
48
 
62
49
  Contact
data/lib/mqtt.rb CHANGED
@@ -17,7 +17,7 @@ module MQTT
17
17
 
18
18
  class ProtocolException < MQTT::Exception
19
19
  end
20
-
20
+
21
21
  class NotConnectedException < MQTT::Exception
22
22
  end
23
23
 
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
- def initialize(remote_host=MQTT::DEFAULT_HOST, remote_port=MQTT::DEFAULT_PORT)
21
- @remote_host = remote_host
22
- @remote_port = remote_port
23
- @keep_alive = 10
24
- @clean_session = true
25
- @client_id = nil
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
- # This method blocks until a message is available.
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
- def get
149
- # Wait for a packet to be available
150
- packet = @read_queue.pop
151
- topic = packet.topic
152
- payload = packet.payload
153
- return topic,payload
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
@@ -1,3 +1,3 @@
1
1
  module MQTT
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -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\x0a\x00\x08myclient"
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\x0a\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
@@ -4,5 +4,5 @@ require 'spec_helper'
4
4
  require 'mqtt'
5
5
 
6
6
  describe MQTT::Proxy do
7
-
7
+
8
8
  end
@@ -13,11 +13,11 @@ describe MQTT do
13
13
  it "should be a string" do
14
14
  MQTT::VERSION.should be_a(String)
15
15
  end
16
-
16
+
17
17
  it "should be in the format x.y.z" do
18
18
  MQTT::VERSION.should =~ /^\d{1,2}\.\d{1,2}\.\d{1,2}$/
19
19
  end
20
-
21
- end
20
+
21
+ end
22
22
 
23
23
  end
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: 21
4
+ hash: 17
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 5
10
- version: 0.0.5
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-17 00:00:00 +00:00
18
+ date: 2012-01-19 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency