mqtt-sn-ruby 0.0.0

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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/mqtt-sn-ruby.rb +451 -0
  3. data/test.rb +61 -0
  4. metadata +45 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aa45e937d94b2d661c19f949faef0d4fb413ea14
4
+ data.tar.gz: a7dfd28feb7a8060ea8a265fc07de0942ff10f20
5
+ SHA512:
6
+ metadata.gz: 0c0663b9a5fbaeca551bb568f24c41bc5f3980a39a5d599b9b863ca91d373605e90e6cd7e8965286d8b60ae48ddf5745a916d36140488f0c5a137bfa83dd2f45
7
+ data.tar.gz: c998f20a123de6d1751cc67d1ff531ae2ec99caf2c3be5fc50cd842463d1d2c6f6768b9564e742c357adeed671f91b5d585deb4d0bde60d3912b5305d7f94167
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env ruby
2
+ # encode: UTF-8
3
+
4
+ require "pp"
5
+ require 'socket'
6
+ require 'json'
7
+
8
+ class MqttSN
9
+
10
+ Nretry = 5 # Max retry
11
+ Tretry = 10 # Timeout before retry
12
+
13
+ CONNECT_TYPE =0x04
14
+ CONNACK_TYPE =0x05
15
+ WILLTOPICREQ_TYPE=0x06
16
+ WILLTOPIC_TYPE =0x07
17
+ WILLMSGREQ_TYPE=0x08
18
+ WILLMSG_TYPE =0x09
19
+ REGISTER_TYPE =0x0A
20
+ REGACK_TYPE =0x0B
21
+ PUBLISH_TYPE =0x0C
22
+ PUBACK_TYPE =0x0D
23
+ PUBCOMP_TYPE =0x0E
24
+ PUBREC_TYPE =0x0F
25
+ PUBREL_TYPE =0x10
26
+ SUBSCRIBE_TYPE =0x12
27
+ SUBACK_TYPE =0x13
28
+ UNSUBSCRIBE_TYPE=0x14
29
+ UNSUBACK_TYPE =0x15
30
+ PINGREQ_TYPE =0x16
31
+ PINGRESP_TYPE =0x17
32
+ DISCONNECT_TYPE=0x18
33
+ WILLTOPICUPD_TYPE=0x1A
34
+ WILLTOPICRESP_TYPE=0x1B
35
+ WILLMSGUPD_TYPE =0x1C
36
+ WILLMSGRESP_TYPE=0x1D
37
+
38
+ RETAIN_FLAG=0x10
39
+ WILL_FLAG =0x08
40
+ CLEAN_FLAG =0x04
41
+ QOSM1_FLAG =0x60
42
+ QOS2_FLAG =0x40
43
+ QOS1_FLAG =0x20
44
+ QOS0_FLAG =0x00
45
+
46
+ @@msg_id=1
47
+
48
+ def initialize(hash={})
49
+ @server=hash[:server]||"127.0.0.1"
50
+ @port=hash[:port]||1883
51
+ @debug=hash[:debug]
52
+ @state=:inited
53
+ @will_topic=nil
54
+ @will_msg=nil
55
+ @id="?"
56
+ @topics={} #hash of registered topics is stored here
57
+ @iq = Queue.new
58
+ @s = UDPSocket.new
59
+ @t=Thread.new do
60
+ recv_thread
61
+ end
62
+ puts "opened ok"
63
+ pp @s
64
+ end
65
+
66
+ def close
67
+ proc { puts "DESTROY OBJECT #{bar}" }
68
+ end
69
+
70
+ def hexdump data
71
+ raw=""
72
+ data.each_byte do |b|
73
+ raw=raw+"," if raw!=""
74
+ raw=raw+sprintf("%02X",b)
75
+ end
76
+ raw
77
+ end
78
+
79
+ def send_packet m
80
+ msg=" "
81
+ len=1
82
+ m.each_with_index do |b,i|
83
+ msg[i+1]=b.chr
84
+ len+=1
85
+ end
86
+ msg[0]=len.chr
87
+ @s.send(msg, 0, @server, @port)
88
+ hexdump msg
89
+ end
90
+
91
+ def send type,hash={},&block
92
+ puts ""
93
+ if @state!=:connected and type!=:connect and type!=:will_topic and type!=:will_msg
94
+ raise "Error: Cannot #{type} while unconnected, send :connect first!"
95
+ end
96
+ case type
97
+ when :connect
98
+ raise "Need :id, it is required at :connect!" if not hash[:id]
99
+ flags=0
100
+ flags+=CLEAN_FLAG if hash[:clean]
101
+ flags+=RETAIN_FLAG if hash[:retain]
102
+ flags+=WILL_FLAG if @will_topic
103
+ p=[CONNECT_TYPE,flags,0x01,0,30]
104
+ hash[:id].each_byte do |b|
105
+ p<<b
106
+ end
107
+ @id=hash[:id]
108
+ when :register
109
+ raise "Need :topic to Publish!" if not hash[:topic]
110
+ p=[REGISTER_TYPE,0,0,@@msg_id >>8 ,@@msg_id & 0xff]
111
+ hash[:topic].each_byte do |b|
112
+ p<<b
113
+ end
114
+ @@msg_id+=1
115
+ when :register_ack
116
+ p=[REGACK_TYPE,hash[:topic_id]>>8 ,hash[:topic_id] & 0xff,hash[:msg_id]>>8 ,hash[:msg_id] & 0xff,hash[:return_code]]
117
+ when :publish_ack
118
+ p=[PUBACK_TYPE,hash[:topic_id]>>8 ,hash[:topic_id] & 0xff,hash[:msg_id]>>8 ,hash[:msg_id] & 0xff,hash[:return_code]]
119
+ when :pub_rec
120
+ p=[PUBREC_TYPE,hash[:msg_id]>>8 ,hash[:msg_id] & 0xff]
121
+ when :pub_comp
122
+ p=[PUBCOMP_TYPE,hash[:msg_id]>>8 ,hash[:msg_id] & 0xff]
123
+ when :will_topic
124
+ raise "Need :topic to :will_topic" if not hash[:topic]
125
+ p=[WILLTOPIC_TYPE,0]
126
+ hash[:topic].each_byte do |b|
127
+ p<<b
128
+ end
129
+ when :will_topic_upd
130
+ raise "Need :topic to :will_topic_upd" if not hash[:topic]
131
+ p=[WILLTOPICUPD_TYPE,0]
132
+ hash[:topic].each_byte do |b|
133
+ p<<b
134
+ end
135
+ when :will_msg
136
+ raise "Need :msg to :will_msg" if not hash[:msg]
137
+ p=[WILLMSG_TYPE]
138
+ hash[:msg].each_byte do |b|
139
+ p<<b
140
+ end
141
+ when :will_msg_upd
142
+ raise "Need :msg to :will_msg_upd" if not hash[:msg]
143
+ p=[WILLMSGUPD_TYPE]
144
+ hash[:msg].each_byte do |b|
145
+ p<<b
146
+ end
147
+
148
+ when :subscribe
149
+ raise "Need :topic to :subscribe" if not hash[:topic]
150
+ qos=hash[:qos]||0
151
+ flags=0
152
+ if qos==-1
153
+ flags+=QOSM1_FLAG
154
+ else
155
+ flags+=QOS1_FLAG*qos
156
+ end
157
+ p=[SUBSCRIBE_TYPE,flags,@@msg_id >>8 ,@@msg_id & 0xff]
158
+ hash[:topic].each_byte do |b|
159
+ p<<b
160
+ end
161
+ @@msg_id+=1
162
+
163
+
164
+ when :publish
165
+ raise "Need :topic_id to Publish!" if not hash[:topic_id]
166
+ qos=hash[:qos]||0
167
+ flags=0
168
+ flags+=RETAIN_FLAG if hash[:retain]
169
+ if qos==-1
170
+ flags+=QOSM1_FLAG
171
+ else
172
+ flags+=QOS1_FLAG*qos
173
+ end
174
+ p=[PUBLISH_TYPE,flags,hash[:topic_id] >>8 ,hash[:topic_id] & 0xff,@@msg_id >>8 ,@@msg_id & 0xff]
175
+ hash[:msg].each_byte do |b|
176
+ p<<b
177
+ end
178
+ @@msg_id+=1
179
+ when :pubrel
180
+ raise "Need the original :msg_id of the Publish for PubRel!" if not hash[:msg_id]
181
+ p=[PUBREL_TYPE,hash[:msg_id] >>8 ,hash[:msg_id] & 0xff]
182
+ when :ping
183
+ p=[PINGREQ_TYPE]
184
+ when :disconnect
185
+ if hash[:duration]
186
+ p=[DISCONNECT_TYPE,hash[:duration] >>8 ,hash[:duration] & 0xff]
187
+ else
188
+ p=[DISCONNECT_TYPE]
189
+ end
190
+ else
191
+ puts "Error: Strange send?? #{type}"
192
+ return nil
193
+ end
194
+ if hash[:expect]
195
+ while not @iq.empty?
196
+ mp=@iq.pop
197
+ puts "WARN:#{@id} ************** Purged message: #{mp}"
198
+ end
199
+ @iq.clear
200
+ end
201
+ raw=send_packet p
202
+ hash[:raw]=raw if @debug
203
+ puts "send:#{@id} #{type},#{hash.to_json}"
204
+ timeout=hash[:timeout]||Tretry
205
+ status=:timeout
206
+ retries=0
207
+ m={}
208
+ if hash[:expect]
209
+ while retries<Nretry do
210
+ stime=Time.now.to_i
211
+ while Time.now.to_i<stime+timeout
212
+ if not @iq.empty?
213
+ m=@iq.pop
214
+ if Array(hash[:expect]).include? m[:type]
215
+ status=:ok
216
+ break
217
+ else
218
+ puts "WARN:#{@id} ************** Discarded message: #{m}"
219
+ end
220
+ end
221
+ sleep 0.1
222
+ end
223
+ if status==:ok
224
+ break
225
+ else
226
+ retries+=1
227
+ send_packet p
228
+ puts "fail to get ack, retry #{retries} :#{@id} #{type},#{hash.to_json}"
229
+ #need to set DUP flag !
230
+ end
231
+ end
232
+
233
+ if block
234
+ block.call status,m
235
+ end
236
+ end
237
+ end
238
+
239
+ def will_and_testament topic,msg
240
+ if @state==:connected #if already connected, send changes, otherwise wait until connect does it.
241
+ if @will_topic!=topic
242
+ send :will_topic_upd, topic: topic, expect: :will_topic_resp do |status,message|
243
+ puts "will topic updated"
244
+ if status==:ok
245
+ else
246
+ puts "Error:#{@id} no pong!"
247
+ end
248
+ end
249
+ end
250
+ if @will_msg!=msg
251
+ send :will_msg_upd, msg: msg, expect: :will_msg_resp do |status,message|
252
+ puts "will msg updated"
253
+ if status==:ok
254
+ else
255
+ puts "Error:#{@id} no pong!"
256
+ end
257
+ end
258
+ end
259
+ end
260
+ @will_topic=topic
261
+ @will_msg=msg
262
+ end
263
+
264
+ def connect id
265
+ send :connect,id: id, clean: true, expect: [:connect_ack,:will_topic_req] do |s,m| #add will here!
266
+ if s==:ok
267
+ if m[:type]==:will_topic_req
268
+ puts "will topic!"
269
+ send :will_topic, topic: @will_topic, expect: [:will_msg_req] do |s,m| #add will here!
270
+ if s==:ok
271
+ puts "will msg!"
272
+ send :will_msg, msg: @will_msg, expect: [:connect_ack] do |s,m|
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ def disconnect
282
+ send :disconnect, expect: :disconnect do |status,message|
283
+ end
284
+ end
285
+
286
+ def goto_sleep duration
287
+ send :disconnect, duration: duration, expect: :disconnect do |status,message|
288
+ end
289
+ end
290
+
291
+ def subscribe topic,hash={}
292
+ send :subscribe, topic: topic, qos: hash[:qos],expect: :sub_ack do |status,message|
293
+ end
294
+ end
295
+
296
+ def ping
297
+ send :ping, timeout: 2,expect: :pong do |status,message|
298
+ if status==:ok
299
+ else
300
+ puts "Error:#{@id} no pong!"
301
+ end
302
+ end
303
+ end
304
+
305
+ def register_topic topic
306
+ send :register,topic: topic, expect: :register_ack do |s,m|
307
+ if s==:ok
308
+ @topics[topic]=m[:topic_id]
309
+ else
310
+ raise "Error:#{@id} Register topic #{topic} failed!"
311
+ end
312
+ pp @topics
313
+ end
314
+ @topics[topic]
315
+ end
316
+
317
+ def publish topic,msg,hash={}
318
+ if not @topics[topic]
319
+ register_topic topic
320
+ end
321
+ case hash[:qos]
322
+ when 1
323
+ send :publish,msg: msg, retain: hash[:retain], topic_id: @topics[topic], qos: 1, expect: [:publish_ack] do |s,m|
324
+ if s==:ok
325
+ puts "got handshaken once! status=#{s}, message=#{m.to_json}"
326
+ end
327
+ end
328
+ when 2
329
+ send :publish,msg: msg, retain: hash[:retain], topic_id: @topics[topic], qos: 2, expect: [:pubrec] do |s,m|
330
+ if s==:ok
331
+ if m[:type]==:pubrec
332
+ send :pubrel,msg_id: m[:msg_id], expect: :pubcomp do |s,m|
333
+ puts "got handshaken twice! status=#{s}, message=#{m.to_json}"
334
+ end
335
+ end
336
+ end
337
+ end
338
+ else
339
+ send :publish,msg: msg, retain: hash[:retain],topic_id: @topics[topic], qos: 0
340
+ end
341
+ end
342
+
343
+ def recv_thread
344
+ while true do
345
+ begin
346
+ r,stuff=@s.recvfrom(200) #_nonblock(200)
347
+ m=nil
348
+ len=r[0].ord
349
+ case r[len-1].ord
350
+ when 0x00
351
+ status=:ok
352
+ when 0x01
353
+ status=:rejected_congestion
354
+ when 0x02
355
+ status=:rejected_invalid_topic_id
356
+ when 0x03
357
+ status=:rejected_not_supported
358
+ else
359
+ status=:unknown_error
360
+ end
361
+ type_byte=r[1].ord
362
+ done=false
363
+ case type_byte
364
+ when CONNACK_TYPE
365
+ m={type: :connect_ack,status: status}
366
+ @state=:connected
367
+
368
+ when SUBACK_TYPE
369
+ topic_id=(r[3].ord<<8)+r[4].ord
370
+ msg_id=(r[5].ord<<8)+r[6].ord
371
+ m={type: :sub_ack, topic_id: topic_id, msg_id: msg_id, status: status}
372
+ when PUBLISH_TYPE
373
+ topic_id=(r[3].ord<<8)+r[4].ord
374
+ msg_id=(r[5].ord<<8)+r[6].ord
375
+ msg=r[7,len-7]
376
+ flags=r[2].ord
377
+ qos=(flags>>5)&0x03
378
+ m={type: :publish, qos: qos, topic_id: topic_id, msg_id: msg_id, msg: msg,status: :ok}
379
+ done=true
380
+ if qos==1 #send ack
381
+ send :publish_ack,topic_id: topic_id, msg_id: msg_id, return_code: 0
382
+ elsif qos==2 #send ack
383
+ send :pub_rec, msg_id: msg_id
384
+ end
385
+ when PUBREL_TYPE
386
+ msg_id=(r[2].ord<<8)+r[3].ord
387
+ send :pub_comp, msg_id: msg_id
388
+ done=true
389
+ when DISCONNECT_TYPE
390
+ m={type: :disconnect,status: :ok}
391
+ @state=:disconnected
392
+ when REGISTER_TYPE
393
+ puts "registering... *************************"
394
+ topic_id=(r[2].ord<<8)+r[3].ord
395
+ msg_id=(r[4].ord<<8)+r[5].ord
396
+ topic=r[6,len-6]
397
+ m={type: :register, topic_id: topic_id, msg_id: msg_id, topic: topic,status: :ok}
398
+ @topics[topic]=m[:topic_id]
399
+ pp @topics
400
+ send :register_ack,topic_id: topic_id, msg_id: msg_id, return_code: 0
401
+ done=true
402
+ when REGACK_TYPE
403
+ topic_id=(r[2].ord<<8)+r[3].ord
404
+ m={type: :register_ack,topic_id: topic_id,status: status}
405
+ #@state=:registered
406
+ when PUBREC_TYPE
407
+ msg_id=(r[2].ord<<8)+r[3].ord
408
+ m={type: :pubrec,msg_id: msg_id,status: :ok}
409
+ when PUBACK_TYPE
410
+ topic_id=(r[2].ord<<8)+r[3].ord
411
+ msg_id=(r[4].ord<<8)+r[5].ord
412
+ m={type: :publish_ack,topic_id: topic_id,msg_id: msg_id, status: status}
413
+ when PUBCOMP_TYPE
414
+ msg_id=(r[2].ord<<8)+r[3].ord
415
+ m={type: :pubcomp,status: :ok, msg_id: msg_id}
416
+
417
+ when WILLTOPICREQ_TYPE
418
+ m={type: :will_topic_req, status: :ok}
419
+ when WILLMSGREQ_TYPE
420
+ m={type: :will_msg_req, status: :ok}
421
+
422
+ when WILLTOPICRESP_TYPE
423
+ m={type: :will_topic_resp, status: :ok}
424
+ when WILLMSGRESP_TYPE
425
+ m={type: :will_msg_resp, status: :ok}
426
+
427
+ when PINGRESP_TYPE
428
+ m={type: :pong, status: :ok}
429
+ else
430
+ m={type: :unknown, type_byte: type_byte }
431
+ end
432
+ if @debug
433
+ m[:raw]=hexdump r
434
+ end
435
+ puts "got :#{@id} #{m.to_json}"
436
+ if m[:type]==:publish
437
+ puts "**************************** PUBLISH"
438
+ end
439
+ if not done
440
+ @iq<<m if m
441
+ end
442
+ rescue IO::WaitReadable
443
+ IO.select([@s])
444
+ retry
445
+ rescue => e
446
+ puts "Error: receive thread died: #{e}"
447
+ pp e.backtrace
448
+ end
449
+ end
450
+ end
451
+ end
data/test.rb ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+ # encode: UTF-8
3
+
4
+ require "pp"
5
+ require 'socket'
6
+ require 'json'
7
+
8
+ if File.file? './lib/mqtt-sn-ruby.rb'
9
+ require './lib/mqtt-sn-ruby.rb'
10
+ puts "using local lib"
11
+ else
12
+ require 'mqtt-sn-ruby'
13
+ puts "using gem lib"
14
+ end
15
+
16
+ puts "Testing mqtt-sn.."
17
+
18
+ sn=MqttSN.new debug: true
19
+ sn2=MqttSN.new debug: false
20
+ sn3=MqttSN.new debug: true
21
+
22
+
23
+ sn.will_and_testament "top","testamentti"
24
+ sn.connect "eka"
25
+
26
+ sn2.connect "toka"
27
+
28
+ sn3.connect "kolmas"
29
+
30
+ sn3.subscribe "eka/+",qos:2
31
+
32
+ topic_id=0
33
+ sn.will_and_testament "top2","testamentti2"
34
+
35
+ sn2.ping
36
+
37
+ #sn.register_topic "jeesus/perkele/toimii"
38
+ sn.publish "eka/1","perkkule0",qos: 0
39
+ sn.publish "eka/2","perkkule1",qos: 1
40
+ sn.publish "eka/3","perkkule2",qos: 2
41
+
42
+ sn2.publish "eka/4","2perkkule0",qos: 0
43
+ sn2.publish "eka/5","2perkkule1rrrr",qos: 1, retain: true
44
+ sn2.publish "eka/6","2perkkule2",qos: 2
45
+ sn2.disconnect
46
+
47
+ #puts "----------------- sleep"
48
+ #sn2.goto_sleep 4
49
+
50
+ 5.times do
51
+ sn.ping
52
+ sleep 0.5
53
+ end
54
+
55
+
56
+ sn.disconnect
57
+ sn3.disconnect
58
+
59
+
60
+ puts "Done testing mqtt-sn!"
61
+
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mqtt-sn-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ari Siitonen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby toolkit for MQTT-SN
14
+ email: jalopuuverstas@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/mqtt-sn-ruby.rb
20
+ - test.rb
21
+ homepage: https://github.com/arisi/mqtt-sn-ruby
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.2.2
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Ruby toolkit for MQTT-SN, compatible with RSMB, command line tools and API
45
+ test_files: []