mqtt-sn-ruby 0.0.8 → 0.0.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc027ab8d8f4c272b23e33ac92f95fb5fdddab86
4
- data.tar.gz: 776240a0b678bad8016f081fcb773d75b9f4ca38
3
+ metadata.gz: fcf699ff94826e2f5267318686ddfaa18218b7cd
4
+ data.tar.gz: cc30f03cfc41694d059ba2ca021a29250d8d6a61
5
5
  SHA512:
6
- metadata.gz: 3bbfea7e20154be77e89c484edfcbd4b561cf0ff4b54d6df29ba59611c81c640bdfb8810b49f9294339deeef4217d476bf5da2b89606836581fab2a0a5675afa
7
- data.tar.gz: dc1f8be26135951a7295e587967013a53986979f4f83069c6611d6232c78e40c38000a8dec310cf1a1a2723e290c4214319aee3574e57fb089d9d0a9963f997c
6
+ metadata.gz: 8274cc0c466571ce851322460dbe47640e14aa5e21cbffbbc6963719190e6d085244a483812a3dd8b2eb45caf9155b2825ce5b0c74a07a20d06f4a0b0eb50cee
7
+ data.tar.gz: b47b0b5d1133eda805df011f18bc648c5ca886e36c7097eacdf75508ec68fa4de03d2011e8bdfabd0be95060c21f91435efe5bc62153e95e8f36af4f1e3e8b7f
@@ -5,6 +5,8 @@ require "pp"
5
5
  require 'socket'
6
6
  require 'json'
7
7
  require 'optparse'
8
+
9
+
8
10
  if File.file? './lib/mqtt-sn-ruby.rb'
9
11
  require './lib/mqtt-sn-ruby.rb'
10
12
  puts "using local lib"
@@ -26,17 +28,40 @@ OptionParser.new do |opts|
26
28
  opts.on("-s", "--server uri", "URI of the MQTT-SN Server to connect to (udp://localhost:1883)") do |v|
27
29
  options[:server_uri] = v
28
30
  end
29
- opts.on("-i", "--localip ip", "MQTT-SN Local ip to bind (127.0.0.1)") do |v|
30
- options[:local_ip] = v
31
- end
32
31
  opts.on("-l", "--localport port", "MQTT-SN local port to listen (1882)") do |v|
33
32
  options[:local_port] = v.to_i
33
+ end
34
+ opts.on("-h", "--http port", "Http port for debug/status JSON server (false)") do |v|
35
+ options[:http_port] = v.to_i
34
36
  end
35
37
  end.parse!
36
38
 
39
+
40
+ if options[:http_port]
41
+ require "sinatra/base"
42
+ puts "Starting HTTP services at port #{options[:http_port]}"
43
+ @hp=options[:http_port]
44
+ class MySinatra < Sinatra::Base
45
+ set :bind, '0.0.0.0'
46
+ set :port, @hp
47
+
48
+ get "/clients" do
49
+ content_type :json
50
+ MqttSN.get_clients.to_json
51
+ end
52
+ get "/gateways" do
53
+ content_type :json
54
+ MqttSN.get_gateways.to_json
55
+ end
56
+ end
57
+ Thread.new do
58
+ MySinatra.run!
59
+ end
60
+ end
61
+ options[:forwarder]=true
37
62
  puts "MQTT-SN-FORWARDER: #{options.to_json}"
38
63
  begin
39
- MqttSN.forwarder options
64
+ f=MqttSN.new options
40
65
  rescue SystemExit, Interrupt
41
66
  puts "\nExiting after Disconnect\n"
42
67
  rescue => e
data/bin/mqtt-sn-pub.rb CHANGED
@@ -44,11 +44,10 @@ puts "MQTT-SN-PUB: #{options.to_json}"
44
44
  begin
45
45
  sn=MqttSN.new options
46
46
  sn.connect options[:id]
47
- sn.send :searchgw, expect: :gwinfo do |status,message|
48
- puts "got gwinfo: #{status}: #{message}"
49
- end
47
+ sn.send :searchgw #replies may or may not come -- even multiple!
50
48
  sn.publish options[:topic]||"test/message/123", options[:msg]||"test_value", qos: options[:qos]
51
49
  puts "Sent ok."
50
+ sleep 2 #allow log tu purge ;)
52
51
  rescue SystemExit, Interrupt
53
52
  puts "\nExiting after Disconnect\n"
54
53
  rescue => e
data/bin/mqtt-sn-sub.rb CHANGED
@@ -35,8 +35,33 @@ OptionParser.new do |opts|
35
35
  opts.on("-t", "--topic topic", "Topic to subscribe (test/message/123)") do |topic|
36
36
  options[:topic] = topic
37
37
  end
38
+ opts.on("-h", "--http port", "Http port for debug/status JSON server (false)") do |v|
39
+ options[:http_port] = v.to_i
40
+ end
41
+
38
42
  end.parse!
39
43
 
44
+
45
+ if options[:http_port]
46
+ puts "Starting HTTP services at port #{options[:http_port]}"
47
+ $hp=options[:http_port]
48
+ Thread.new do
49
+ server = TCPServer.new("20.20.20.21",8083)
50
+ loop do
51
+ Thread.start(server.accept) do |client|
52
+ response= MqttSN::get_gateways.to_json
53
+ client.print "HTTP/1.1 200 OK\r\n" +
54
+ "Content-Type: text/json\r\n" +
55
+ "Content-Length: #{response.bytesize}\r\n" +
56
+ "Connection: close\r\n"
57
+ client.print "\r\n"
58
+ client.print response
59
+ client.close
60
+ end
61
+ end
62
+ end
63
+ end
64
+
40
65
  puts "MQTT-SN-SUB: #{options.to_json}"
41
66
  begin
42
67
  sn=MqttSN.new options
data/lib/mqtt-sn-ruby.rb CHANGED
@@ -5,11 +5,15 @@ require "pp"
5
5
  require 'socket'
6
6
  require 'json'
7
7
  require 'uri'
8
+ require 'ipaddr'
9
+
8
10
 
9
11
  class MqttSN
10
12
 
11
13
  Nretry = 5 # Max retry
12
14
  Tretry = 10 # Timeout before retry
15
+
16
+ MULTICAST_ADDR = "225.4.5.6"
13
17
 
14
18
  SEARCHGW_TYPE =0x01
15
19
  GWINFO_TYPE =0x02
@@ -48,16 +52,24 @@ class MqttSN
48
52
  QOS0_FLAG =0x00
49
53
 
50
54
  @@msg_id=1
55
+ @@clients={}
56
+ @@gateways={}
57
+ @@log_q = Queue.new #log queue :)
58
+ @@log_t=Thread.new do
59
+ log_thread
60
+ end
51
61
 
52
- def open_port uri_s
62
+ def self.logger str,*args
63
+ @@log_q << sprintf(str,*args)
64
+ end
65
+
66
+ def self.open_port uri_s
53
67
  begin
54
68
  uri = URI.parse(uri_s)
55
69
  pp uri
56
70
  if uri.scheme== 'udp'
57
- @server=uri.host
58
- @port=uri.port
59
71
  puts "server: #{@server}:#{@port}"
60
- return UDPSocket.new
72
+ return [UDPSocket.new,uri.host,uri.port]
61
73
  else
62
74
  raise "Error: Cannot open socket for '#{uri_s}', unsupported scheme: '#{uri.scheme}'"
63
75
  end
@@ -67,13 +79,29 @@ class MqttSN
67
79
  end
68
80
  end
69
81
 
82
+ def self.open_multicast_send_port port
83
+ ip = IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new("0.0.0.0").hton
84
+ socket_b = UDPSocket.new
85
+ socket_b.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, [1].pack('i'))
86
+ socket_b
87
+ end
88
+
89
+ def self.open_multicast_recv_port port
90
+ ip = IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new("0.0.0.0").hton
91
+ s = UDPSocket.new
92
+ s.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, ip)
93
+ s.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
94
+ s.bind(Socket::INADDR_ANY, port)
95
+ s
96
+ end
97
+
70
98
  def initialize(hash={})
71
- #BasicSocket.do_not_reverse_lookup = true
72
99
  @options=hash #save these
73
100
  @server_uri=hash[:server_uri]||"udp://localhost:1883"
74
101
  @debug=hash[:debug]
75
102
  @verbose=hash[:verbose]
76
103
  @state=:inited
104
+ @bcast_port=5000
77
105
  @forward=hash[:forward] #flag to indicate forward mode
78
106
  @will_topic=nil
79
107
  @will_msg=nil
@@ -82,26 +110,116 @@ class MqttSN
82
110
  @topics={} #hash of registered topics is stored here
83
111
  @iq = Queue.new
84
112
  @dataq = Queue.new
85
- @s = open_port @server_uri
86
- pp @s
87
- @t=Thread.new do
88
- recv_thread
89
- end
90
- @bcast=nil
91
- if true
92
- begin
93
- @bcast = UDPSocket.open
94
- @bcast.bind('0.0.0.0', 1882)
95
- @roam_t=Thread.new do
96
- roam_thread
97
- end
98
- rescue => e
99
- puts "Error: Could not open bcast port!"
100
- @bcast=nil
113
+ @s,@server,@port = MqttSN::open_port @server_uri
114
+
115
+ @bcast_s=MqttSN::open_multicast_send_port @bcast_port
116
+ @bcast=MqttSN::open_multicast_recv_port @bcast_port
117
+
118
+ @roam_t=Thread.new do
119
+ roam_thread @bcast
120
+ end
121
+ if not @options[:forwarder]
122
+ @client_t=Thread.new do
123
+ client_thread @s
124
+ end
125
+ else
126
+ @s.bind("0.0.0.0",hash[:local_port]||1883)
127
+ @bcast_period=60
128
+ loop do
129
+ forwarder
130
+ end
131
+ end
132
+
133
+ end
134
+
135
+
136
+ def forwarder
137
+ begin
138
+ last_bcast=0
139
+ last_kill=0
140
+ stime=Time.now.to_i
141
+ while true
142
+ now=Time.now.to_i
143
+ if now>last_bcast+@bcast_period
144
+ #periodically broadcast :advertize
145
+ MqttSN::send_packet [ADVERTISE_TYPE,0xAB,0,30],@bcast_s,MULTICAST_ADDR,@bcast_port
146
+ last_bcast=now
147
+ end
148
+ #periodically kill disconnected clients -- and those timed out..
149
+ if now>last_kill+1
150
+ changes=false
151
+ @@clients.dup.each do |key,data|
152
+ if data[:state]==:disconnected
153
+ dest="#{data[:ip]}:#{data[:port]}"
154
+ MqttSN::logger "- %s",dest
155
+ @@clients.delete key
156
+ changes=true
157
+ end
158
+ end
159
+ if changes
160
+ MqttSN::logger "cli:#{@@clients.to_json}"
161
+ end
162
+ last_kill=now
163
+ end
164
+
165
+ if false # pac=MqttSN::poll_packet(@bcast)
166
+ r,client_ip,client_port=pac
167
+ key="#{client_ip}:#{client_port}"
168
+ dest="#{MULTICAST_ADDR};#{@bcast_port}"
169
+ m=MqttSN::parse_message r
170
+ MqttSN::logger "R %-18.18s -> %-18.18s | %s",key,dest,m.to_json
171
+
172
+ case m[:type]
173
+ when :searchgw
174
+ dest="#{client_ip}:#{client_port}"
175
+ MqttSN::logger "C %-18.18s -> %-18.18s | %s",key,dest,m.to_json
176
+ MqttSN::send_packet [GWINFO_TYPE,0xAB],@bcast_s,MULTICAST_ADDR,@bcast_port
177
+ done=true
178
+ end
179
+
180
+ end
181
+ if pac=MqttSN::poll_packet(@s)
182
+ r,client_ip,client_port=pac
183
+ key="#{client_ip}:#{client_port}"
184
+ if not @@clients[key]
185
+ @@clients[key]={ip:client_ip, port:client_port, socket: UDPSocket.new, state: :active }
186
+ dest="#{client_ip}:#{client_port}"
187
+ MqttSN::logger "+ %s\n",dest
188
+ MqttSN::logger "cli: #{@@clients.to_json}"
189
+ end
190
+ @@clients[key][:stamp]=Time.now.to_i
191
+ m=MqttSN::parse_message r
192
+ done=false
193
+
194
+ if not done # not done locally -> forward it
195
+ sbytes=@@clients[key][:socket].send(r, 0, @server, @port) # to rsmb -- ok as is
196
+ _,port,_,_ = @@clients[key][:socket].addr
197
+ dest="#{@server}:#{port}"
198
+ MqttSN::logger "C %-18.18s -> %-18.18s | %s",key,dest,m.to_json
199
+ end
200
+ end
201
+ @@clients.each do |key,c|
202
+ if pac=MqttSN::poll_packet(c[:socket])
203
+ r,client_ip,client_port=pac
204
+ #puts "got packet #{pac} from server to client #{key}:"
205
+ #puts "sending to #{c[:ip]}:#{c[:port]}"
206
+ @s.send(r, 0, c[:ip], c[:port]) # send_packet
207
+ m=MqttSN::parse_message r
208
+ _,port,_,_ = @@clients[key][:socket].addr
209
+ dest="#{@server}:#{port}"
210
+ MqttSN::logger "S %-18.18s <- %-18.18s | %s",key,dest,m.to_json
211
+ case m[:type]
212
+ when :disconnect
213
+ @@clients[key][:state]=:disconnected
214
+ end
215
+ end
101
216
  end
102
- end
217
+ sleep 0.01
218
+ end
219
+ end
103
220
  end
104
221
 
222
+
105
223
  def self.hexdump data
106
224
  raw=""
107
225
  data.each_byte do |b|
@@ -250,11 +368,15 @@ class MqttSN
250
368
  end
251
369
  @iq.clear
252
370
  end
253
- raw=send_packet p
371
+ if type==:searchgw
372
+ raw=MqttSN::send_packet p,@bcast,MULTICAST_ADDR,@bcast_port
373
+ else
374
+ raw=MqttSN::send_packet p,@s,@server,@port
375
+ end
254
376
  hash[:debug]=raw if @debug
255
377
  #puts "send:#{@id} #{type},#{hash.to_json}" if @verbose
256
378
  timeout=hash[:timeout]||Tretry
257
- retries=0
379
+ retries=0
258
380
  if hash[:expect]
259
381
  while retries<Nretry do
260
382
  stime=Time.now.to_i
@@ -288,8 +410,12 @@ class MqttSN
288
410
  end
289
411
 
290
412
  def self.send_raw_packet msg,socket,server,port
291
- socket.send(msg, 0, server, port)
292
- MqttSN::hexdump msg
413
+ if socket
414
+ socket.send(msg, 0, server, port)
415
+ MqttSN::hexdump msg
416
+ else
417
+ puts "Error: no socket at send_raw_packet"
418
+ end
293
419
  end
294
420
 
295
421
  def self.send_packet m,socket,server,port
@@ -298,7 +424,7 @@ class MqttSN
298
424
  dest="#{server}:#{port}"
299
425
  _,port,_,_ = socket.addr
300
426
  src=":#{port}"
301
- printf "< %-18.18s <- %-18.18s | %s\n",dest,src,parse_message(msg).to_json
427
+ MqttSN::logger "< %-18.18s <- %-18.18s | %s",dest,src,parse_message(msg).to_json
302
428
  end
303
429
 
304
430
 
@@ -321,80 +447,15 @@ class MqttSN
321
447
  return nil
322
448
  end
323
449
 
324
- def self.forwarder hash={}
325
- @options=hash #save these
326
- @server=hash[:server]||"127.0.0.1"
327
- @port=hash[:port]||1883
328
- @debug=hash[:debug]
329
- @verbose=hash[:verbose]
330
-
331
- socket_b = UDPSocket.new
332
- socket_b.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
333
- #socket_b.bind("0.0.0.0",1882)
450
+ def self.get_clients
451
+ @@clients
452
+ end
334
453
 
335
- socket = UDPSocket.new
336
- socket.bind(hash[:local_ip]||"127.0.0.1",hash[:local_port]||0)
337
-
338
- clients={}
339
- begin
340
- last=0
341
- stime=Time.now.to_i
342
- while true
343
- now=Time.now.to_i
344
- if now>last+10
345
- MqttSN::send_packet [ADVERTISE_TYPE,0xAB,0,30],socket_b,"255.255.255.255",1882
346
- last=now
347
- end
348
- #periodically kill disconnected clients -- and those timed out..
349
- #periodically broadcast :advertize
350
- if pac=poll_packet(socket)
351
- r,client_ip,client_port=pac
352
- key="#{client_ip}:#{client_port}"
353
- if not clients[key]
354
- clients[key]={ip:client_ip, port:client_port, socket: UDPSocket.new, state: :active }
355
- dest="#{client_ip}:#{client_port}"
356
- printf "+ %s\n",dest
357
- end
358
- clients[key][:stamp]=Time.now.to_i
359
- m=MqttSN::parse_message r
360
- done=false
361
- case m[:type]
362
- when :searchgw
363
- #do it here! and reply with :gwinfo
364
- dest="#{client_ip}:#{client_port}"
365
- printf "C %-18.18s -> %-18.18s | %s\n",key,dest,m.to_json
366
- MqttSN::send_packet [GWINFO_TYPE,0xAB],socket,client_ip,client_port
367
- done=true
368
- end
369
- if not done # not done locally -> forward it
370
- sbytes=clients[key][:socket].send(r, 0, @server, @port) # to rsmb -- ok as is
371
- _,port,_,_ = clients[key][:socket].addr
372
- dest="#{@server}:#{port}"
373
- printf "C %-18.18s -> %-18.18s | %s\n",key,dest,m.to_json
374
- end
375
- end
376
- clients.each do |key,c|
377
- if pac=poll_packet(c[:socket])
378
- r,client_ip,client_port=pac
379
- #puts "got packet #{pac} from server to client #{key}:"
380
- #puts "sending to #{c[:ip]}:#{c[:port]}"
381
- socket.send(r, 0, c[:ip], c[:port]) # send_packet
382
- m=MqttSN::parse_message r
383
- _,port,_,_ = clients[key][:socket].addr
384
- dest="#{@server}:#{port}"
385
- printf "S %-18.18s <- %-18.18s | %s\n",key,dest,m.to_json
386
- case m[:type]
387
- when :disconnect
388
- puts "disco -- kill me!"
389
- clients[key][:state]=:disconnected
390
- end
391
- end
392
- end
393
- sleep 0.01
394
- end
395
- end
454
+ def self.get_gateways
455
+ @@gateways
396
456
  end
397
457
 
458
+
398
459
  def will_and_testament topic,msg
399
460
  if @state==:connected #if already connected, send changes, otherwise wait until connect does it.
400
461
  if @will_topic!=topic
@@ -645,12 +706,12 @@ class MqttSN
645
706
  end
646
707
  when :connect_ack
647
708
  @state=:connected
709
+ when :searchgw
710
+ done=true
648
711
  when :advertise
649
- puts "hey, we have router there!"
650
712
  done=true
651
713
  when :gwinfo
652
- puts "hey, we have router there!"
653
- #done=true
714
+ done=true
654
715
  end
655
716
  # puts "got :#{@id} #{m.to_json}" if @verbose
656
717
  if not done
@@ -659,11 +720,55 @@ class MqttSN
659
720
  m
660
721
  end
661
722
 
662
- def recv_thread
723
+ def client_thread socket
724
+ while true do
725
+ begin
726
+ if pac=MqttSN::poll_packet(socket)
727
+ r,client_ip,client_port=pac
728
+ m=MqttSN::parse_message r
729
+ if @debug and m
730
+ m[:debug]=MqttSN::hexdump r
731
+ end
732
+ dest="#{client_ip}:#{client_port}"
733
+ _,port,_,_ = @s.addr
734
+ src=port
735
+ MqttSN::logger "> %-18.18s <- %-18.18s | %s",dest,":#{port}",m.to_json
736
+ process_message m
737
+ end
738
+ rescue => e
739
+ puts "Error: receive thread died: #{e}"
740
+ pp e.backtrace
741
+ end
742
+ end
743
+ end
744
+
745
+ def process_broadcast_message m,client_ip,client_port
746
+ case m[:type]
747
+ when :searchgw
748
+ MqttSN::logger "hey, someone is looking for gateway..."
749
+ key="#{client_ip}:#{client_port}"
750
+ dest="#{MULTICAST_ADDR};#{@bcast_port}"
751
+ #actually -- send data on all gateways we know...
752
+ MqttSN::logger "r %-18.18s -> %-18.18s | %s",key,dest,m.to_json
753
+ MqttSN::send_packet [GWINFO_TYPE,0xAB],@bcast_s,MULTICAST_ADDR,@bcast_port
754
+ when :advertise,:gwinfo
755
+ MqttSN::logger "hey, we have gateway there!"
756
+ if not @@gateways[client_ip]
757
+ @@gateways[client_ip]={stamp: Time.now.to_i}
758
+ else
759
+ @@gateways[client_ip][:stamp]=Time.now.to_i
760
+ end
761
+ @@gateways[client_ip][:gw_id]=m[:gw_id]
762
+ @@gateways[client_ip][:duration]=m[:duration]
763
+ MqttSN::logger "gw: #{@@gateways.to_json}"
764
+ end
765
+ end
766
+
767
+ def roam_thread socket
663
768
  while true do
664
769
  begin
665
770
  if @bcast
666
- if pac=MqttSN::poll_packet(@bcast)
771
+ if pac=MqttSN::poll_packet(socket)
667
772
  r,client_ip,client_port=pac
668
773
  m=MqttSN::parse_message r
669
774
  if @debug and m
@@ -672,22 +777,10 @@ class MqttSN
672
777
  dest="#{client_ip}:#{client_port}"
673
778
  _,port,_,_ = @bcast.addr
674
779
  src=port
675
- printf "R %-18.18s <- %-18.18s | %s\n",dest,":#{port}",m.to_json
676
- process_message m
780
+ MqttSN::logger "R %-18.18s <- %-18.18s | %s",dest,":#{port}",m.to_json
781
+ process_broadcast_message m,client_ip,client_port
677
782
  end
678
783
  end
679
- if pac=MqttSN::poll_packet(@s)
680
- r,client_ip,client_port=pac
681
- m=MqttSN::parse_message r
682
- if @debug and m
683
- m[:debug]=MqttSN::hexdump r
684
- end
685
- dest="#{client_ip}:#{client_port}"
686
- _,port,_,_ = @s.addr
687
- src=port
688
- printf "> %-18.18s <- %-18.18s | %s\n",dest,":#{port}",m.to_json
689
- process_message m
690
- end
691
784
  rescue => e
692
785
  puts "Error: receive thread died: #{e}"
693
786
  pp e.backtrace
@@ -695,9 +788,18 @@ class MqttSN
695
788
  end
696
789
  end
697
790
 
698
- def roam_thread
791
+ def self.log_thread
699
792
  while true do
700
- sleep 1
793
+ begin
794
+ if not @@log_q.empty?
795
+ l=@@log_q.pop
796
+ puts l
797
+ end
798
+ rescue => e
799
+ puts "Error: receive thread died: #{e}"
800
+ pp e.backtrace
801
+ end
701
802
  end
702
803
  end
804
+
703
805
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mqtt-sn-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ari Siitonen