mqtt 0.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.
- data/COPYING +56 -0
- data/NEWS +5 -0
- data/README +47 -0
- data/Rakefile +112 -0
- data/examples/simple_get.rb +18 -0
- data/examples/simple_publish.rb +12 -0
- data/lib/mqtt/client.rb +238 -0
- data/lib/mqtt/packet.rb +160 -0
- data/lib/mqtt.rb +39 -0
- metadata +63 -0
data/COPYING
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
ruby-mqtt is copyrighted free software by Nicholas J Humfrey <njh@aelius.com>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the GPL
|
3
|
+
version 2 (see the file GPL), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) give non-standard binaries non-standard names, with
|
21
|
+
instructions on where to get the original software distribution.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or binary form,
|
26
|
+
provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the binaries and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard binaries non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under these terms.
|
43
|
+
|
44
|
+
For the list of those files and their copying conditions, see the
|
45
|
+
file LEGAL.
|
46
|
+
|
47
|
+
5. The scripts and library files supplied as input to or produced as
|
48
|
+
output from the software do not automatically fall under the
|
49
|
+
copyright of the software, but belong to whomever generated them,
|
50
|
+
and may be sold commercially, and may be aggregated with this
|
51
|
+
software.
|
52
|
+
|
53
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
54
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
55
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
56
|
+
PURPOSE.
|
data/NEWS
ADDED
data/README
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
= ruby-mqtt
|
2
|
+
|
3
|
+
Pure Ruby gem that implements the MQTT (Message Queue Telemetry Transport) protocol, a lightweight protocol for publish/subscribe messaging.
|
4
|
+
|
5
|
+
RubyForge Project Page http://rubyforge.org/projects/mqtt/
|
6
|
+
|
7
|
+
== Installing
|
8
|
+
|
9
|
+
You may get the latest stable version from Rubyforge. Source gems are also available.
|
10
|
+
|
11
|
+
$ gem install mqtt
|
12
|
+
|
13
|
+
== Synopsis
|
14
|
+
|
15
|
+
require 'rubygems'
|
16
|
+
require 'mqtt'
|
17
|
+
|
18
|
+
mqtt = MQTT::Client.new('mqtt.example.com')
|
19
|
+
mqtt.connect('clientid') do |c|
|
20
|
+
c.publish('topic','message')
|
21
|
+
end
|
22
|
+
|
23
|
+
== TODO
|
24
|
+
|
25
|
+
* Support payloads longer than 128 bytes
|
26
|
+
* Process acknowledgement packets
|
27
|
+
* Create classes for each type of packet?
|
28
|
+
* More validations of data/parameters
|
29
|
+
* Implement exception throwing
|
30
|
+
* Implement Will and Testament
|
31
|
+
* Add unit tests
|
32
|
+
* More examples
|
33
|
+
* Refactor to add callbacks that are called from seperate thread
|
34
|
+
* Implement QOS Level 1
|
35
|
+
* Implement QOS Level 2
|
36
|
+
* Add support for binding socket to specific local address
|
37
|
+
|
38
|
+
== Resources
|
39
|
+
|
40
|
+
http://mqtt.org
|
41
|
+
|
42
|
+
== Contact
|
43
|
+
|
44
|
+
Author:: Nicholas J Humfrey
|
45
|
+
Email:: njh@aelius.com
|
46
|
+
Home Page:: http://www.aelius.com/njh/
|
47
|
+
License:: Distributes under the same terms as Ruby
|
data/Rakefile
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
require 'spec/rake/verify_rcov'
|
8
|
+
|
9
|
+
|
10
|
+
NAME = "mqtt"
|
11
|
+
VERS = "0.0.1"
|
12
|
+
CLEAN.include ['pkg', 'rdoc']
|
13
|
+
|
14
|
+
spec = Gem::Specification.new do |s|
|
15
|
+
s.name = NAME
|
16
|
+
s.version = VERS
|
17
|
+
s.author = "Nicholas J Humfrey"
|
18
|
+
s.email = "njh@aelius.com"
|
19
|
+
s.homepage = "http://mqtt.rubyforge.org"
|
20
|
+
s.platform = Gem::Platform::RUBY
|
21
|
+
s.summary = "Implementation of the MQTT (Message Queue Telemetry Transport) protocol"
|
22
|
+
s.rubyforge_project = "mqtt"
|
23
|
+
s.description = "Pure Ruby gem that implements the MQTT (Message Queue Telemetry Transport) protocol, a lightweight protocol for publish/subscribe messaging."
|
24
|
+
s.files = FileList["Rakefile", "lib/*.rb", "lib/mqtt/*.rb", "examples/*"]
|
25
|
+
s.require_path = "lib"
|
26
|
+
|
27
|
+
# rdoc
|
28
|
+
s.has_rdoc = true
|
29
|
+
s.extra_rdoc_files = ["README", "NEWS", "COPYING"]
|
30
|
+
|
31
|
+
# Build Dependencies
|
32
|
+
#s.add_dependency 'rake' '~> 0.8'
|
33
|
+
#s.add_dependency 'rspec', '~> 1.1'
|
34
|
+
#s.add_dependency 'rcov', '~> 0.8'
|
35
|
+
#s.add_dependency 'mocha', '~> 0.9'
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Default: test the gem."
|
39
|
+
task :default => [:check_syntax, :rdoc]
|
40
|
+
|
41
|
+
task :build_package => [:repackage]
|
42
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
43
|
+
pkg.need_zip = false
|
44
|
+
pkg.need_tar = true
|
45
|
+
pkg.gem_spec = spec
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "Run :package and install the resulting .gem"
|
49
|
+
task :install => :package do
|
50
|
+
sh %{sudo gem install --local pkg/#{NAME}-#{VERS}.gem}
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Run :clean and uninstall the .gem"
|
54
|
+
task :uninstall => :clean do
|
55
|
+
sh %{sudo gem uninstall #{NAME}}
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
## Testing
|
61
|
+
desc "Run all the specification tests"
|
62
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
63
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
64
|
+
t.spec_opts = ["--colour"]
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "Check the syntax of all ruby files"
|
68
|
+
task :check_syntax do
|
69
|
+
`find . -name "*.rb" |xargs -n1 ruby -c |grep -v "Syntax OK"`
|
70
|
+
puts "* Done"
|
71
|
+
end
|
72
|
+
|
73
|
+
namespace :spec do
|
74
|
+
desc "Generate RCov report"
|
75
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
76
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
77
|
+
t.rcov = true
|
78
|
+
t.rcov_dir = 'coverage'
|
79
|
+
t.rcov_opts = ['--text-report', '--exclude', "spec/"]
|
80
|
+
end
|
81
|
+
|
82
|
+
desc "Generate specdoc"
|
83
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
84
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
85
|
+
t.spec_opts = ["--format", "specdoc"]
|
86
|
+
end
|
87
|
+
|
88
|
+
namespace :doc do
|
89
|
+
desc "Generate html specdoc"
|
90
|
+
Spec::Rake::SpecTask.new(:html) do |t|
|
91
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
92
|
+
t.spec_opts = ["--format", "html:rspec_report.html", "--diff"]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
## Documentation
|
100
|
+
desc "Generate documentation for the library"
|
101
|
+
Rake::RDocTask.new("rdoc") { |rdoc|
|
102
|
+
rdoc.rdoc_dir = 'rdoc'
|
103
|
+
rdoc.title = "mqtt Documentation"
|
104
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
105
|
+
rdoc.main = "README"
|
106
|
+
rdoc.rdoc_files.include("README", "NEWS", "COPYING", "lib/*.rb", "lib/mqtt/*.rb")
|
107
|
+
}
|
108
|
+
|
109
|
+
desc "Upload rdoc to rubyforge"
|
110
|
+
task :upload_rdoc => [:rdoc] do
|
111
|
+
sh %{/usr/bin/scp -r -p rdoc/* mqtt.rubyforge.org:/var/www/gforge-projects/mqtt}
|
112
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__)+'/../lib'
|
4
|
+
|
5
|
+
require 'mqtt'
|
6
|
+
|
7
|
+
|
8
|
+
client = MQTT::Client.new('mqtt.example.com')
|
9
|
+
client.connect('simple_get')
|
10
|
+
|
11
|
+
client.subscribe('$SYS/#')
|
12
|
+
|
13
|
+
loop do
|
14
|
+
topic,message = client.get
|
15
|
+
puts "#{topic}: #{message}"
|
16
|
+
end
|
17
|
+
|
18
|
+
client.disconnect
|
data/lib/mqtt/client.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mqtt'
|
4
|
+
require 'mqtt/packet'
|
5
|
+
require 'thread'
|
6
|
+
require 'socket'
|
7
|
+
|
8
|
+
|
9
|
+
module MQTT
|
10
|
+
|
11
|
+
# Client class for talking to an MQTT broker
|
12
|
+
class Client
|
13
|
+
attr_reader :remote_host # Hostname of the remote broker
|
14
|
+
attr_reader :remote_port # Port number of the remote broker
|
15
|
+
attr_accessor :keep_alive # Time between pings to remote broker
|
16
|
+
attr_accessor :clean_start # Set the 'Clean Start' flag when connecting?
|
17
|
+
|
18
|
+
# Timeout between select polls (in seconds)
|
19
|
+
SELECT_TIMEOUT = 0.5
|
20
|
+
|
21
|
+
# Create a new MQTT Client instance
|
22
|
+
def initialize(remote_host='localhost', remote_port=1883)
|
23
|
+
@remote_host = remote_host
|
24
|
+
@remote_port = remote_port
|
25
|
+
@message_id = 0
|
26
|
+
@keep_alive = 10
|
27
|
+
@clean_start = true
|
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
|
+
end
|
35
|
+
|
36
|
+
# Connect to the MQTT broker
|
37
|
+
# If a block is given, then yield to that block and then disconnect again.
|
38
|
+
def connect(clientid)
|
39
|
+
if not connected?
|
40
|
+
# Create network socket
|
41
|
+
@socket = TCPSocket.new(@remote_host,@remote_port)
|
42
|
+
|
43
|
+
# Start packet reading thread
|
44
|
+
@read_thread = Thread.new(Thread.current) do |parent|
|
45
|
+
Thread.current[:parent] = parent
|
46
|
+
loop { receive_packet }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Protocol name and version
|
50
|
+
packet = MQTT::Packet.new(:type => :connect)
|
51
|
+
packet.add_string('MQIsdp')
|
52
|
+
packet.add_bytes(0x03)
|
53
|
+
|
54
|
+
# Connect flags
|
55
|
+
connect_flags = 0x00
|
56
|
+
connect_flags ||= 0x02 if @clean_start
|
57
|
+
# FIXME: implement Will and Testament
|
58
|
+
packet.add_bytes(connect_flags)
|
59
|
+
|
60
|
+
# Keep Alive timer: 10 seconds
|
61
|
+
packet.add_short(@keep_alive)
|
62
|
+
|
63
|
+
# Add the client identifier
|
64
|
+
packet.add_string(clientid)
|
65
|
+
|
66
|
+
# Send packet
|
67
|
+
send_packet(packet)
|
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.new(:type => :disconnect)
|
83
|
+
send_packet(packet)
|
84
|
+
end
|
85
|
+
@read_thread.kill unless @read_thread.nil?
|
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.new(:type => :pingreq)
|
100
|
+
send_packet(packet)
|
101
|
+
@last_pingreq = Time.now
|
102
|
+
end
|
103
|
+
|
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.new(
|
107
|
+
:type => :publish,
|
108
|
+
:qos => qos,
|
109
|
+
:retain => retain
|
110
|
+
)
|
111
|
+
|
112
|
+
# Add the topic name
|
113
|
+
packet.add_string(topic)
|
114
|
+
|
115
|
+
# Add Message ID for qos1 and qos2
|
116
|
+
unless qos == 0
|
117
|
+
packet.add_short(@message_id.next)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Add the packet payload
|
121
|
+
packet.add_data(payload)
|
122
|
+
|
123
|
+
# Send the packet
|
124
|
+
send_packet(packet)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Send a subscribe message for one or more topics on the MQTT broker.
|
128
|
+
# The topics parameter should be one of the following:
|
129
|
+
# * String: subscribe to one topic with QOS 0
|
130
|
+
# * Array: subscribe to multiple topics with QOS 0
|
131
|
+
# * Hash: subscribe to multiple topics where the key is the topic and the value is the QOS level
|
132
|
+
#
|
133
|
+
# For example:
|
134
|
+
# client.subscribe( 'a/b' )
|
135
|
+
# client.subscribe( 'a/b', 'c/d' )
|
136
|
+
# client.subscribe( ['a/b',0], ['c/d',1] )
|
137
|
+
# client.subscribe( 'a/b' => 0, 'c/d' => 1 )
|
138
|
+
#
|
139
|
+
def subscribe(*topics)
|
140
|
+
array = []
|
141
|
+
topics.each do |item|
|
142
|
+
if item.is_a?(Hash)
|
143
|
+
# Convert hash into an ordered array of arrays
|
144
|
+
array += item.sort
|
145
|
+
elsif item.is_a?(Array)
|
146
|
+
# Already in [topic,qos] format
|
147
|
+
array.push item
|
148
|
+
else
|
149
|
+
# Default to QOS 0
|
150
|
+
array.push [item.to_s,0]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Create the packet
|
155
|
+
packet = MQTT::Packet.new(:type => :subscribe, :qos => 1)
|
156
|
+
packet.add_short(@message_id.next)
|
157
|
+
array.each do |item|
|
158
|
+
packet.add_string(item[0])
|
159
|
+
packet.add_bytes(item[1])
|
160
|
+
end
|
161
|
+
send_packet(packet)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Return the next message recieved from the MQTT broker.
|
165
|
+
# This method blocks until a message is available.
|
166
|
+
#
|
167
|
+
# The method returns the topic and message as an array:
|
168
|
+
# topic,message = client.get
|
169
|
+
#
|
170
|
+
def get
|
171
|
+
# Wait for a packet to be available
|
172
|
+
packet = @read_queue.pop
|
173
|
+
|
174
|
+
# Parse the variable header
|
175
|
+
topic = packet.shift_string
|
176
|
+
msg_id = packet.shift_short unless (packet.qos == 0)
|
177
|
+
return topic,packet.body
|
178
|
+
end
|
179
|
+
|
180
|
+
# Send a unsubscribe message for one or more topics on the MQTT broker
|
181
|
+
def unsubscribe(*topics)
|
182
|
+
packet = MQTT::Packet.new(:type => :unsubscribe, :qos => 1)
|
183
|
+
topics.each { |topic| packet.add_string(topic) }
|
184
|
+
send_packet(packet)
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
# Try to read a packet from the broker
|
190
|
+
# Also sends keep-alive ping packets.
|
191
|
+
def receive_packet
|
192
|
+
begin
|
193
|
+
# Poll socket - is there data waiting?
|
194
|
+
result = IO.select([@socket], nil, nil, SELECT_TIMEOUT)
|
195
|
+
unless result.nil?
|
196
|
+
# Yes - read in the packet
|
197
|
+
packet = MQTT::Packet.read(@socket)
|
198
|
+
if packet.type == :publish
|
199
|
+
# Add to queue
|
200
|
+
@read_queue.push(packet)
|
201
|
+
else
|
202
|
+
# Ignore all other packets
|
203
|
+
nil
|
204
|
+
# FIXME: implement responses for QOS 1 and 2
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Time to send a keep-alive ping request?
|
209
|
+
if Time.now > @last_pingreq + @keep_alive
|
210
|
+
ping
|
211
|
+
end
|
212
|
+
|
213
|
+
# FIXME: check we received a ping response recently?
|
214
|
+
|
215
|
+
# Pass exceptions up to parent thread
|
216
|
+
rescue Exception => exp
|
217
|
+
unless @socket.nil?
|
218
|
+
@socket.close
|
219
|
+
@socket = nil
|
220
|
+
end
|
221
|
+
Thread.current[:parent].raise(exp)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Send a packet to broker
|
226
|
+
def send_packet(data)
|
227
|
+
# Throw exception if we aren't connected
|
228
|
+
raise MQTT::NotConnectedException if not connected?
|
229
|
+
|
230
|
+
# Only allow one thread to write to socket at a time
|
231
|
+
@write_semaphore.synchronize do
|
232
|
+
@socket.write(data)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
data/lib/mqtt/packet.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mqtt'
|
4
|
+
|
5
|
+
module MQTT
|
6
|
+
|
7
|
+
# Class representing a MQTT Packet
|
8
|
+
# Performs binary encoding and decoding of headers
|
9
|
+
class Packet #:nodoc: all
|
10
|
+
attr_reader :type # The packet type
|
11
|
+
attr_reader :dup # Duplicate delivery flag
|
12
|
+
attr_reader :retain # Retain flag
|
13
|
+
attr_reader :qos # Quality of Service level
|
14
|
+
attr_reader :body # Packet's body (everything after fixed header)
|
15
|
+
|
16
|
+
# Read in a packet from a socket
|
17
|
+
def self.read(sock)
|
18
|
+
header = sock.read(2)
|
19
|
+
raise MQTT::ProtocolException if header.nil?
|
20
|
+
byte1,byte2 = header.unpack('C*')
|
21
|
+
|
22
|
+
# FIXME: support decoding of multi-byte length header
|
23
|
+
|
24
|
+
packet = MQTT::Packet.new(
|
25
|
+
:type => ((byte1 & 0xF0) >> 4),
|
26
|
+
:dup => ((byte1 & 0x08) >> 3),
|
27
|
+
:qos => ((byte1 & 0x06) >> 1),
|
28
|
+
:retain => ((byte1 & 0x01) >> 0)
|
29
|
+
)
|
30
|
+
packet.body = sock.read(byte2)
|
31
|
+
|
32
|
+
return packet
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create a new empty packet
|
36
|
+
def initialize(args={})
|
37
|
+
self.type = args[:type] || :invalid
|
38
|
+
self.dup = args[:dup] || false
|
39
|
+
self.qos = args[:qos] || 0
|
40
|
+
self.retain = args[:retain] || false
|
41
|
+
self.body = args[:body] || ''
|
42
|
+
end
|
43
|
+
|
44
|
+
def type=(arg)
|
45
|
+
if arg.kind_of?(Integer)
|
46
|
+
# Convert type identifier to symbol
|
47
|
+
@type = MQTT::PACKET_TYPES[arg]
|
48
|
+
else
|
49
|
+
@type = arg.to_sym
|
50
|
+
# FIXME: raise exception if packet type is invalid?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return the identifer for this packet type
|
55
|
+
def type_id
|
56
|
+
raise "No packet type set for this packet" if @type.nil?
|
57
|
+
index = MQTT::PACKET_TYPES.index(@type)
|
58
|
+
raise "Invalid packet type: #{@type}" if index.nil?
|
59
|
+
return index
|
60
|
+
end
|
61
|
+
|
62
|
+
def dup=(arg)
|
63
|
+
if arg.kind_of?(Integer)
|
64
|
+
@dup = (arg != 0 ? true : false)
|
65
|
+
else
|
66
|
+
@dup = arg
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def retain=(arg)
|
71
|
+
if arg.kind_of?(Integer)
|
72
|
+
@retain = (arg != 0 ? true : false)
|
73
|
+
else
|
74
|
+
@retain = arg
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def qos=(arg)
|
79
|
+
@qos = arg.to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
def body=(arg)
|
83
|
+
@body = arg.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
# Add an array of bytes to the end of the packet's body
|
91
|
+
def add_bytes(*bytes)
|
92
|
+
@body += bytes.pack('C*')
|
93
|
+
end
|
94
|
+
|
95
|
+
# Add a 16-bit unsigned integer to the end of the packet's body
|
96
|
+
def add_short(val)
|
97
|
+
@body += [val.to_i].pack('n')
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add some raw data to the end of the packet's body
|
101
|
+
def add_data(data)
|
102
|
+
data = data.to_s unless data.is_a?(String)
|
103
|
+
@body += data
|
104
|
+
end
|
105
|
+
|
106
|
+
# Add a string to the end of the packet's body
|
107
|
+
# (preceded by the length of the string)
|
108
|
+
def add_string(str)
|
109
|
+
str = str.to_s unless str.is_a?(String)
|
110
|
+
add_short(str.size)
|
111
|
+
add_data(str)
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
# Remove a 16-bit unsigned integer from the front on the body
|
116
|
+
def shift_short
|
117
|
+
bytes = @body.slice!(0..1)
|
118
|
+
bytes.unpack('n').first
|
119
|
+
end
|
120
|
+
|
121
|
+
# Remove n bytes from the front on the body
|
122
|
+
def shift_bytes(bytes)
|
123
|
+
@body.slice!(0...bytes).unpack('C*')
|
124
|
+
end
|
125
|
+
|
126
|
+
# Remove n bytes from the front on the body
|
127
|
+
def shift_data(bytes)
|
128
|
+
@body.slice!(0...bytes)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Remove string from the front on the body
|
132
|
+
def shift_string
|
133
|
+
len = shift_short
|
134
|
+
shift_data(len)
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
# Serialise the packet
|
139
|
+
def to_s
|
140
|
+
# Encode the 2-byte fixed header
|
141
|
+
header = [
|
142
|
+
((type_id.to_i & 0x0F) << 4) |
|
143
|
+
((dup ? 0x1 : 0x0) << 3) |
|
144
|
+
((qos.to_i & 0x03) << 1) |
|
145
|
+
(retain ? 0x1 : 0x0),
|
146
|
+
(@body.length & 0x7F)
|
147
|
+
]
|
148
|
+
# FIXME: support multi-byte length header
|
149
|
+
header.pack('C*') + @body
|
150
|
+
end
|
151
|
+
|
152
|
+
def inspect
|
153
|
+
format("#<MQTT::Packet:0x%01x ", object_id)+
|
154
|
+
"type=#{@type}, dup=#{@dup}, retain=#{@retain}, "+
|
155
|
+
"qos=#{@qos}, body.size=#{@body.size}>"
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
data/lib/mqtt.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mqtt/client'
|
4
|
+
|
5
|
+
# Pure-ruby implementation of the MQTT protocol
|
6
|
+
module MQTT
|
7
|
+
|
8
|
+
PACKET_TYPES = [
|
9
|
+
nil,
|
10
|
+
:connect, # Client request to connect to Broker
|
11
|
+
:connack, # Connect Acknowledgment
|
12
|
+
:publish, # Publish message
|
13
|
+
:puback, # Publish Acknowledgment
|
14
|
+
:pubrec, # Publish Received (assured delivery part 1)
|
15
|
+
:pubrel, # Publish Release (assured delivery part 2)
|
16
|
+
:pubcomp, # Publish Complete (assured delivery part 3)
|
17
|
+
:subscribe, # Client Subscribe request
|
18
|
+
:suback, # Subscribe Acknowledgment
|
19
|
+
:unsubscribe, # Client Unsubscribe request
|
20
|
+
:unsuback, # Unsubscribe Acknowledgment
|
21
|
+
:pingreq, # PING Request
|
22
|
+
:pingresp, # PING Response
|
23
|
+
:disconnect, # Client is Disconnecting
|
24
|
+
nil
|
25
|
+
]
|
26
|
+
|
27
|
+
class Exception < Exception
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class ProtocolException < MQTT::Exception
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class NotConnectedException < MQTT::Exception
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mqtt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nicholas J Humfrey
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-01 00:00:00 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Pure Ruby gem that implements the MQTT (Message Queue Telemetry Transport) protocol, a lightweight protocol for publish/subscribe messaging.
|
17
|
+
email: njh@aelius.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- NEWS
|
25
|
+
- COPYING
|
26
|
+
files:
|
27
|
+
- Rakefile
|
28
|
+
- lib/mqtt.rb
|
29
|
+
- lib/mqtt/client.rb
|
30
|
+
- lib/mqtt/packet.rb
|
31
|
+
- examples/simple_get.rb
|
32
|
+
- examples/simple_publish.rb
|
33
|
+
- README
|
34
|
+
- NEWS
|
35
|
+
- COPYING
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://mqtt.rubyforge.org
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project: mqtt
|
58
|
+
rubygems_version: 1.3.1
|
59
|
+
signing_key:
|
60
|
+
specification_version: 2
|
61
|
+
summary: Implementation of the MQTT (Message Queue Telemetry Transport) protocol
|
62
|
+
test_files: []
|
63
|
+
|