mqtt 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mqtt.rb CHANGED
@@ -1,18 +1,28 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # Pure-ruby implementation of the MQTT protocol
3
+ require 'logger'
4
+ require 'socket'
5
+ require 'thread'
6
+ require 'timeout'
7
+
8
+ require "mqtt/version"
9
+
4
10
  module MQTT
5
11
 
12
+ DEFAULT_HOST = 'localhost'
13
+ DEFAULT_PORT = 1883
14
+
6
15
  class Exception < Exception
7
-
8
16
  end
9
17
 
10
18
  class ProtocolException < MQTT::Exception
11
-
12
19
  end
13
20
 
14
21
  class NotConnectedException < MQTT::Exception
15
-
16
22
  end
17
23
 
24
+ autoload :Client, 'mqtt/client'
25
+ autoload :Packet, 'mqtt/packet'
26
+ autoload :Proxy, 'mqtt/proxy'
27
+
18
28
  end
data/lib/mqtt/client.rb CHANGED
@@ -1,245 +1,246 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'mqtt'
4
- require 'mqtt/packet'
5
- require 'thread'
6
- require 'socket'
7
- require 'timeout'
8
-
9
-
10
- module MQTT
11
-
12
- # Client class for talking to an MQTT broker
13
- class Client
14
- attr_reader :remote_host # Hostname of the remote broker
15
- attr_reader :remote_port # Port number of the remote broker
16
- attr_accessor :keep_alive # Time (in seconds) between pings to remote broker
17
- attr_accessor :clean_start # Set the 'Clean Start' flag when connecting?
18
- attr_accessor :client_id # Client Identifier
19
- attr_accessor :ack_timeout # Number of seconds to wait for acknowledgement packets
20
-
21
- # Timeout between select polls (in seconds)
22
- SELECT_TIMEOUT = 0.5
23
-
24
- # Create a new MQTT Client instance
25
- def initialize(remote_host='localhost', remote_port=1883)
26
- @remote_host = remote_host
27
- @remote_port = remote_port
28
- @keep_alive = 10
29
- @clean_start = true
1
+ # Client class for talking to an MQTT broker
2
+ class MQTT::Client
3
+ attr_reader :remote_host # Hostname of the remote broker
4
+ attr_reader :remote_port # Port number of the remote broker
5
+ attr_accessor :keep_alive # Time (in seconds) between pings to remote broker
6
+ attr_accessor :clean_session # Set the 'Clean Session' flag when connecting?
7
+ attr_accessor :client_id # Client Identifier
8
+ attr_accessor :ack_timeout # Number of seconds to wait for acknowledgement packets
9
+ attr_accessor :username # Username to authenticate to the broker with
10
+ attr_accessor :password # Password to authenticate to the broker with
11
+
12
+ # OLD deprecated clean_start
13
+ alias :clean_start :clean_session
14
+ alias :clean_start= :clean_session=
15
+
16
+ # Timeout between select polls (in seconds)
17
+ SELECT_TIMEOUT = 0.5
18
+
19
+ # 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
26
+ @message_id = 0
27
+ @ack_timeout = 5
28
+ @last_pingreq = Time.now
29
+ @last_pingresp = Time.now
30
+ @socket = nil
31
+ @read_queue = Queue.new
32
+ @read_thread = nil
33
+ @write_semaphore = Mutex.new
34
+ @username = nil
35
+ @password = nil
36
+ end
37
+
38
+ # Connect to the MQTT broker
39
+ # If a block is given, then yield to that block and then disconnect again.
40
+ def connect(clientid=nil)
41
+ if !clientid.nil?
42
+ @client_id = clientid
43
+ elsif clientid.nil?
30
44
  @client_id = random_letters(16)
31
- @message_id = 0
32
- @ack_timeout = 5
33
- @last_pingreq = Time.now
34
- @last_pingresp = Time.now
35
- @socket = nil
36
- @read_queue = Queue.new
37
- @read_thread = nil
38
- @write_semaphore = Mutex.new
45
+ @clean_session = true
39
46
  end
40
-
41
- # Connect to the MQTT broker
42
- # If a block is given, then yield to that block and then disconnect again.
43
- def connect(clientid=nil)
44
- @client_id = clientid unless clientid.nil?
45
-
46
- if not connected?
47
- # Create network socket
48
- @socket = TCPSocket.new(@remote_host,@remote_port)
49
-
50
- # Protocol name and version
51
- packet = MQTT::Packet::Connect.new(
52
- :clean_start => @clean_start,
53
- :keep_alive => @keep_alive,
54
- :client_id => @client_id
55
- )
56
-
57
- # Send packet
58
- send_packet(packet)
59
47
 
60
- # Receive response
61
- receive_connack
62
-
63
- # Start packet reading thread
64
- @read_thread = Thread.new(Thread.current) do |parent|
65
- Thread.current[:parent] = parent
66
- loop { receive_packet }
67
- end
68
- end
69
-
70
- # If a block is given, then yield and disconnect
71
- if block_given?
72
- yield(self)
73
- disconnect
74
- end
75
- end
76
-
77
- # Disconnect from the MQTT broker.
78
- # If you don't want to say goodbye to the broker, set send_msg to false.
79
- def disconnect(send_msg=true)
80
- if connected?
81
- if send_msg
82
- packet = MQTT::Packet::Disconnect.new
83
- send_packet(packet)
84
- end
85
- @read_thread.kill if @read_thread and @read_thread.alive?
86
- @read_thread = nil
87
- @socket.close unless @socket.nil?
88
- @socket = nil
89
- end
90
- end
91
-
92
- # Checks whether the client is connected to the broker.
93
- def connected?
94
- not @socket.nil?
95
- end
96
-
97
- # Send a MQTT ping message to indicate that the MQTT client is alive.
98
- def ping
99
- packet = MQTT::Packet::Pingreq.new
100
- send_packet(packet)
101
- @last_pingreq = Time.now
102
- end
48
+ if not connected?
49
+ # Create network socket
50
+ @socket = TCPSocket.new(@remote_host,@remote_port)
103
51
 
104
- # Publish a message on a particular topic to the MQTT broker.
105
- def publish(topic, payload, retain=false, qos=0)
106
- packet = MQTT::Packet::Publish.new(
107
- :qos => qos,
108
- :retain => retain,
109
- :topic => topic,
110
- :payload => payload,
111
- :message_id => @message_id.next
52
+ # Protocol name and version
53
+ packet = MQTT::Packet::Connect.new(
54
+ :clean_session => @clean_session,
55
+ :keep_alive => @keep_alive,
56
+ :client_id => @client_id,
57
+ :username => @username,
58
+ :password => @password
112
59
  )
113
60
 
114
- # Send the packet
61
+ # Send packet
115
62
  send_packet(packet)
63
+
64
+ # Receive response
65
+ receive_connack
66
+
67
+ # Start packet reading thread
68
+ @read_thread = Thread.new(Thread.current) do |parent|
69
+ Thread.current[:parent] = parent
70
+ loop { receive_packet }
71
+ end
116
72
  end
117
-
118
- # Send a subscribe message for one or more topics on the MQTT broker.
119
- # The topics parameter should be one of the following:
120
- # * String: subscribe to one topic with QOS 0
121
- # * Array: subscribe to multiple topics with QOS 0
122
- # * Hash: subscribe to multiple topics where the key is the topic and the value is the QOS level
123
- #
124
- # For example:
125
- # client.subscribe( 'a/b' )
126
- # client.subscribe( 'a/b', 'c/d' )
127
- # client.subscribe( ['a/b',0], ['c/d',1] )
128
- # client.subscribe( 'a/b' => 0, 'c/d' => 1 )
129
- #
130
- def subscribe(*topics)
131
- packet = MQTT::Packet::Subscribe.new(
132
- :topics => topics,
133
- :message_id => @message_id.next
134
- )
135
- send_packet(packet)
136
- end
137
-
138
- # Return the next message recieved from the MQTT broker.
139
- # This method blocks until a message is available.
140
- #
141
- # The method returns the topic and message as an array:
142
- # topic,message = client.get
143
- #
144
- def get
145
- # Wait for a packet to be available
146
- packet = @read_queue.pop
147
- topic = packet.topic
148
- payload = packet.payload
149
- return topic,payload
150
- end
151
-
152
- # Send a unsubscribe message for one or more topics on the MQTT broker
153
- def unsubscribe(*topics)
154
- packet = MQTT::Packet::Unsubscribe.new(
155
- :topics => topics,
156
- :message_id => @message_id.next
157
- )
158
- send_packet(packet)
73
+
74
+ # If a block is given, then yield and disconnect
75
+ if block_given?
76
+ yield(self)
77
+ disconnect
159
78
  end
160
-
161
- private
162
-
163
- # Try to read a packet from the broker
164
- # Also sends keep-alive ping packets.
165
- def receive_packet
166
- begin
167
- # Poll socket - is there data waiting?
168
- result = IO.select([@socket], nil, nil, SELECT_TIMEOUT)
169
- unless result.nil?
170
- # Yes - read in the packet
171
- packet = MQTT::Packet.read(@socket)
172
- if packet.class == MQTT::Packet::Publish
173
- # Add to queue
174
- @read_queue.push(packet)
175
- else
176
- # Ignore all other packets
177
- nil
178
- # FIXME: implement responses for QOS 1 and 2
179
- end
180
- end
181
-
182
- # Time to send a keep-alive ping request?
183
- if Time.now > @last_pingreq + @keep_alive
184
- ping
185
- end
186
-
187
- # FIXME: check we received a ping response recently?
188
-
189
- # Pass exceptions up to parent thread
190
- rescue Exception => exp
191
- unless @socket.nil?
192
- @socket.close
193
- @socket = nil
194
- end
195
- Thread.current[:parent].raise(exp)
79
+ end
80
+
81
+ # Disconnect from the MQTT broker.
82
+ # If you don't want to say goodbye to the broker, set send_msg to false.
83
+ def disconnect(send_msg=true)
84
+ if connected?
85
+ if send_msg
86
+ packet = MQTT::Packet::Disconnect.new
87
+ send_packet(packet)
196
88
  end
89
+ @read_thread.kill if @read_thread and @read_thread.alive?
90
+ @read_thread = nil
91
+ @socket.close unless @socket.nil?
92
+ @socket = nil
197
93
  end
198
-
199
- # Read and check a connection acknowledgement packet
200
- def receive_connack
201
- Timeout.timeout(@ack_timeout) do
94
+ end
95
+
96
+ # Checks whether the client is connected to the broker.
97
+ def connected?
98
+ not @socket.nil?
99
+ end
100
+
101
+ # Send a MQTT ping message to indicate that the MQTT client is alive.
102
+ def ping
103
+ packet = MQTT::Packet::Pingreq.new
104
+ send_packet(packet)
105
+ @last_pingreq = Time.now
106
+ end
107
+
108
+ # Publish a message on a particular topic to the MQTT broker.
109
+ def publish(topic, payload, retain=false, qos=0)
110
+ packet = MQTT::Packet::Publish.new(
111
+ :qos => qos,
112
+ :retain => retain,
113
+ :topic => topic,
114
+ :payload => payload,
115
+ :message_id => @message_id.next
116
+ )
117
+
118
+ # Send the packet
119
+ send_packet(packet)
120
+ end
121
+
122
+ # Send a subscribe message for one or more topics on the MQTT broker.
123
+ # The topics parameter should be one of the following:
124
+ # * String: subscribe to one topic with QOS 0
125
+ # * Array: subscribe to multiple topics with QOS 0
126
+ # * Hash: subscribe to multiple topics where the key is the topic and the value is the QOS level
127
+ #
128
+ # For example:
129
+ # client.subscribe( 'a/b' )
130
+ # client.subscribe( 'a/b', 'c/d' )
131
+ # client.subscribe( ['a/b',0], ['c/d',1] )
132
+ # client.subscribe( 'a/b' => 0, 'c/d' => 1 )
133
+ #
134
+ def subscribe(*topics)
135
+ packet = MQTT::Packet::Subscribe.new(
136
+ :topics => topics,
137
+ :message_id => @message_id.next
138
+ )
139
+ send_packet(packet)
140
+ end
141
+
142
+ # Return the next message recieved from the MQTT broker.
143
+ # This method blocks until a message is available.
144
+ #
145
+ # The method returns the topic and message as an array:
146
+ # topic,message = client.get
147
+ #
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
154
+ end
155
+
156
+ # Send a unsubscribe message for one or more topics on the MQTT broker
157
+ def unsubscribe(*topics)
158
+ packet = MQTT::Packet::Unsubscribe.new(
159
+ :topics => topics,
160
+ :message_id => @message_id.next
161
+ )
162
+ send_packet(packet)
163
+ end
164
+
165
+ private
166
+
167
+ # Try to read a packet from the broker
168
+ # Also sends keep-alive ping packets.
169
+ def receive_packet
170
+ begin
171
+ # Poll socket - is there data waiting?
172
+ result = IO.select([@socket], nil, nil, SELECT_TIMEOUT)
173
+ unless result.nil?
174
+ # Yes - read in the packet
202
175
  packet = MQTT::Packet.read(@socket)
203
- if packet.class != MQTT::Packet::Connack
204
- raise MQTT::ProtocolException.new("Response wan't a connection acknowledgement: #{packet.class}")
205
- end
206
-
207
- # Check the return code
208
- if packet.return_code != 0x00
209
- raise MQTT::ProtocolException.new(packet.return_msg)
176
+ if packet.class == MQTT::Packet::Publish
177
+ # Add to queue
178
+ @read_queue.push(packet)
179
+ else
180
+ # Ignore all other packets
181
+ nil
182
+ # FIXME: implement responses for QOS 1 and 2
210
183
  end
211
184
  end
185
+
186
+ # Time to send a keep-alive ping request?
187
+ if Time.now > @last_pingreq + @keep_alive
188
+ ping
189
+ end
190
+
191
+ # FIXME: check we received a ping response recently?
192
+
193
+ # Pass exceptions up to parent thread
194
+ rescue Exception => exp
195
+ unless @socket.nil?
196
+ @socket.close
197
+ @socket = nil
198
+ end
199
+ Thread.current[:parent].raise(exp)
212
200
  end
213
-
214
- # Send a packet to broker
215
- def send_packet(data)
216
- # Throw exception if we aren't connected
217
- raise MQTT::NotConnectedException if not connected?
218
-
219
- # Only allow one thread to write to socket at a time
220
- @write_semaphore.synchronize do
221
- @socket.write(data)
201
+ end
202
+
203
+ # Read and check a connection acknowledgement packet
204
+ def receive_connack
205
+ Timeout.timeout(@ack_timeout) do
206
+ packet = MQTT::Packet.read(@socket)
207
+ if packet.class != MQTT::Packet::Connack
208
+ raise MQTT::ProtocolException.new("Response wan't a connection acknowledgement: #{packet.class}")
209
+ end
210
+
211
+ # Check the return code
212
+ if packet.return_code != 0x00
213
+ raise MQTT::ProtocolException.new(packet.return_msg)
222
214
  end
223
215
  end
216
+ end
224
217
 
225
- # Generate a string of random letters (0-9,a-z)
226
- def random_letters(count)
227
- str = ''
228
- count.times do
229
- num = rand(36)
230
- if (num<10)
231
- # Number
232
- num += 48
233
- else
234
- # Letter
235
- num += 87
236
- end
237
- str += num.chr
218
+ # Send a packet to broker
219
+ def send_packet(data)
220
+ # Throw exception if we aren't connected
221
+ raise MQTT::NotConnectedException if not connected?
222
+
223
+ # Only allow one thread to write to socket at a time
224
+ @write_semaphore.synchronize do
225
+ @socket.write(data)
226
+ end
227
+ end
228
+
229
+ # Generate a string of random letters (0-9,a-z)
230
+ def random_letters(count)
231
+ str = ''
232
+ count.times do
233
+ num = rand(36)
234
+ if (num<10)
235
+ # Number
236
+ num += 48
237
+ else
238
+ # Letter
239
+ num += 87
238
240
  end
239
- return str
241
+ str += num.chr
240
242
  end
241
-
242
-
243
+ return str
243
244
  end
244
245
 
245
246
  end