mqtt-sub_handler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cc152d507d338bd439708e9506b55c434f4ebeb4
4
+ data.tar.gz: 7b9ce380c5ceda9d935d0defe51794723295376d
5
+ SHA512:
6
+ metadata.gz: a03ca9562da63543356deedfbba1117c877f5ebc80dac1da5883fbdb4e6a57e2eb17e28bb6bd554f999c9c97170a2dffe7a39a973ba79b0663fb9f09c2be4628
7
+ data.tar.gz: aa8c44e52e0a2ff8893fa721f23be336770b7248bbcbd678d220b82d3a93e4cfb1d8f9795e79424f446f346eadb0a90b2b6339035b132e793da661ea5a3b5604
@@ -0,0 +1,44 @@
1
+ require 'timeout'
2
+
3
+ module Xasin
4
+ class Waitpoint
5
+ attr_reader :lastArgument
6
+
7
+ def initialize()
8
+ @waitThreads = Array.new();
9
+ @fireID = 0;
10
+ @lastArgument = Array.new();
11
+ end
12
+
13
+ def fire(args = nil)
14
+ @fireID += 1
15
+ @lastArgument = args;
16
+ @waitThreads.each do |t|
17
+ t.run();
18
+ end
19
+ end
20
+
21
+ def wait(seconds = nil, allow_run: false)
22
+ pausedID = @fireID;
23
+ @waitThreads << Thread.current
24
+
25
+ timed_out = false;
26
+
27
+ begin
28
+ Timeout::timeout(seconds) {
29
+ if(allow_run) then
30
+ Thread.stop();
31
+ else
32
+ Thread.stop() until pausedID != @fireID;
33
+ end
34
+ }
35
+ rescue Timeout::Error
36
+ timed_out = true;
37
+ ensure
38
+ @waitThreads.delete(Thread.current);
39
+ end
40
+
41
+ return timed_out, @lastArgument;
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,289 @@
1
+
2
+ require 'timeout'
3
+ require 'mqtt'
4
+
5
+ require 'mqtt/subscription_classes'
6
+
7
+ module MQTT
8
+ def self.Eclipse()
9
+ @EclipseMQTT ||= SubHandler.new('iot.eclipse.org');
10
+ return @EclipseMQTT;
11
+ end
12
+
13
+ class SubHandler
14
+ def self.getTopicSplit(topicName)
15
+ return topicName.scan(/[^\/]+/);
16
+ end
17
+
18
+ def self.getTopicMatch(receivedTopicString, topicPattern)
19
+ receivedTopicList = getTopicSplit receivedTopicString;
20
+
21
+ outputTopicList = Array.new();
22
+
23
+ return nil unless receivedTopicList.length >= topicPattern.length;
24
+
25
+ topicPattern.each_index do |i|
26
+ if(topicPattern[i] == "+") then
27
+ outputTopicList << receivedTopicList[i];
28
+
29
+ elsif(topicPattern[i] == "#") then
30
+ outputTopicList.concat receivedTopicList[i..-1];
31
+ return outputTopicList;
32
+
33
+ elsif topicPattern[i] != receivedTopicList[i];
34
+ return nil;
35
+
36
+ end
37
+ end
38
+
39
+ return outputTopicList if topicPattern.length == receivedTopicList.length;
40
+ return nil;
41
+ end
42
+
43
+ def call_interested(topic, data)
44
+ topicHasReceivers = false;
45
+ @callbackList.each do |h|
46
+ tMatch = SubHandler.getTopicMatch(topic, h.topic_split);
47
+ if tMatch
48
+ h.offer(tMatch, data)
49
+ topicHasReceivers = true;
50
+ end
51
+ end
52
+
53
+ @mqtt.unsubscribe(topic) unless topicHasReceivers;
54
+ end
55
+ private :call_interested
56
+
57
+ def raw_subscribe_to(topic, qos: 1)
58
+ begin
59
+ @conChangeMutex.lock
60
+ if not @connected then
61
+ @subscribeQueue << [topic, qos];
62
+ @conChangeMutex.unlock
63
+ else
64
+ @conChangeMutex.unlock
65
+ @mqtt.subscribe(topic => qos);
66
+ end
67
+ rescue MQTT::Exception, SocketError, SystemCallError
68
+ sleep 0.05;
69
+ retry
70
+ end
71
+ end
72
+ private :raw_subscribe_to
73
+
74
+ def unregister_subscription(subObject)
75
+ raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscription
76
+ return unless @callbackList.include? subObject;
77
+
78
+ @callbackList.delete(subObject);
79
+ end
80
+ def register_subscription(subObject)
81
+ raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscription
82
+ return if @callbackList.include? subObject;
83
+
84
+ @callbackList << subObject;
85
+ raw_subscribe_to(subObject.topic, qos: subObject.qos);
86
+ end
87
+
88
+ def wait_for(topic, qos: 1, timeout: nil)
89
+ subObject = MQTT::WaitpointSubscription.new(topic, qos);
90
+ register_subscription(subObject);
91
+
92
+ if block_given? then
93
+ begin
94
+ Timeout::timeout(timeout) do
95
+ loop do
96
+ return_data = subObject.waitpoint.wait()[1];
97
+ if yield(return_data[0], return_data[1]) then
98
+ unregister_subscription(subObject);
99
+ return true;
100
+ end
101
+ end
102
+ end
103
+ rescue Timeout::Error
104
+ return false;
105
+ end
106
+ else
107
+ return_data = subObject.waitpoint.wait(timeout);
108
+ end
109
+
110
+ unregister_subscription(subObject);
111
+ return return_data;
112
+ end
113
+ def track(topic, qos: 1, &callback)
114
+ unless(@trackerHash.has_key? topic)
115
+ subObject = MQTT::ValueTrackerSubscription.new(topic, qos);
116
+ register_subscription(subObject);
117
+
118
+ @trackerHash[topic] = subObject;
119
+ end
120
+
121
+ @trackerHash[topic].attach(callback) if(callback)
122
+
123
+ return @trackerHash[topic];
124
+ end
125
+ alias on_change track
126
+ def subscribe_to(topic, qos: 1, &callback)
127
+ subObject = MQTT::CallbackSubscription.new(topic, qos, callback);
128
+ register_subscription(subObject);
129
+
130
+ return subObject;
131
+ end
132
+ alias subscribeTo subscribe_to
133
+
134
+
135
+ def publish_to(topic, data, qos: 1, retain: false)
136
+ raise ArgumentError, "Wrong symbol in topic: #{topic}" if topic =~ /[#\+]/
137
+
138
+ begin
139
+ @conChangeMutex.lock
140
+ if not @connected then
141
+ @publishQueue << {topic: topic, data: data, qos: qos, retain: retain} unless qos == 0
142
+ @conChangeMutex.unlock
143
+ else
144
+ @conChangeMutex.unlock
145
+ @mqtt.publish(topic, data, retain);
146
+ end
147
+ rescue MQTT::Exception, SocketError, SystemCallError
148
+ sleep 0.05;
149
+ retry
150
+ end
151
+ end
152
+ alias publishTo publish_to
153
+
154
+ def mqtt_resub_thread
155
+ while(true)
156
+ begin
157
+ Timeout::timeout(10) {
158
+ @mqtt.connect()
159
+ }
160
+ @conChangeMutex.synchronize {
161
+ @connected = true;
162
+ }
163
+ until @subscribeQueue.empty? do
164
+ h = @subscribeQueue[-1];
165
+ @mqtt.subscribe(h[0] => h[1]);
166
+ @subscribeQueue.pop;
167
+ sleep 0.01
168
+ end
169
+ until @publishQueue.empty? do
170
+ h = @publishQueue[-1];
171
+ @mqtt.publish(h[:topic], h[:data], h[:retain]);
172
+ @publishQueue.pop;
173
+ sleep 0.01
174
+ end
175
+ @mqtt.get do |topic, message|
176
+ call_interested(topic, message);
177
+ end
178
+ rescue MQTT::Exception, Timeout::Error, SocketError, SystemCallError
179
+ @connected = false;
180
+
181
+ @conChangeMutex.unlock if @conChangeMutex.owned?
182
+ @mqtt.clean_session=false;
183
+ sleep 2
184
+ end
185
+ end
186
+ end
187
+ private :mqtt_resub_thread
188
+
189
+ def lockAndListen()
190
+ Signal.trap("INT") {
191
+ exit 0
192
+ }
193
+
194
+ puts "Main thread paused."
195
+ Thread.stop();
196
+ end
197
+ def flush_pubqueue()
198
+ puts "\n";
199
+ if @publishQueue.empty? then
200
+ puts "MQTT buffer empty, continuing."
201
+ else
202
+ print "Finishing sending of MQTT messages ... "
203
+ begin
204
+ Timeout::timeout(10) {
205
+ until @publishQueue.empty? do
206
+ sleep 0.05;
207
+ end
208
+ }
209
+ rescue Timeout::Error
210
+ puts "Timed out, aborting."
211
+ else
212
+ puts "Done."
213
+ end
214
+ end
215
+ end
216
+
217
+ def initialize(mqttClient, autoListen: true)
218
+ @callbackList = Array.new();
219
+ if mqttClient.is_a? String then
220
+ @mqtt = MQTT::Client.new(mqttClient);
221
+ else
222
+ @mqtt = mqttClient;
223
+ end
224
+
225
+ @conChangeMutex = Mutex.new();
226
+ @connected = false;
227
+
228
+ @mqtt.client_id ||= MQTT::Client.generate_client_id("MQTT_Sub_", 8);
229
+
230
+ @publishQueue = Array.new();
231
+ @subscribeQueue = Array.new();
232
+ @subscribedTopics = Hash.new();
233
+
234
+ @trackerHash = Hash.new();
235
+
236
+ @listenerThread = Thread.new do
237
+ if @mqtt.clean_session
238
+ @mqttWasStartedClean = true;
239
+ begin
240
+ @mqtt.connect();
241
+ @mqtt.disconnect();
242
+ rescue MQTT::Exception
243
+ sleep 1;
244
+ retry
245
+ rescue SocketError, SystemCallError
246
+ sleep 5
247
+ retry
248
+ end
249
+ @mqtt.clean_session=false;
250
+ end
251
+
252
+ mqtt_resub_thread
253
+ end
254
+ @listenerThread.abort_on_exception = true;
255
+
256
+ at_exit {
257
+ flush_pubqueue
258
+ @listenerThread.kill();
259
+
260
+ if(@mqttWasStartedClean) then
261
+ print "Logging out of mqtt server... "
262
+ begin
263
+ Timeout::timeout(10) {
264
+ begin
265
+ @mqtt.clean_session = true;
266
+ @mqtt.disconnect();
267
+ @mqtt.connect();
268
+ rescue MQTT::Exception, SocketError, SystemCallError
269
+ sleep 1
270
+ retry;
271
+ end
272
+ }
273
+ rescue Timeout::Error
274
+ puts "Timed out, aborting!";
275
+ else
276
+ puts "Done."
277
+ end
278
+ end
279
+ }
280
+
281
+ begin
282
+ Timeout::timeout(10) {
283
+ until(@connected) do sleep 0.1; end
284
+ }
285
+ rescue Timeout::Error
286
+ end
287
+ end
288
+ end
289
+ end
@@ -0,0 +1,75 @@
1
+
2
+ require 'mqtt/Waitpoint.rb'
3
+
4
+ module MQTT
5
+ class Subscription
6
+ attr_reader :topic
7
+ attr_reader :qos
8
+ attr_reader :topic_split
9
+
10
+ def initialize(topic, qos)
11
+ @topic = topic;
12
+ @topic_split = SubHandler.getTopicSplit(topic);
13
+
14
+ @qos = 0;
15
+ end
16
+
17
+ def offer(topicList, data) end
18
+ end
19
+
20
+ class CallbackSubscription < Subscription
21
+ def initialize(topic, qos, callback)
22
+ super(topic, qos);
23
+
24
+ @callback = callback;
25
+ end
26
+ def offer(topicList, data)
27
+ @callback.call(data, topicList);
28
+ end
29
+ end
30
+
31
+ class WaitpointSubscription < Subscription
32
+ attr_reader :waitpoint
33
+
34
+ def initialize(topic, qos)
35
+ super(topic, qos);
36
+
37
+ @waitpoint = Xasin::Waitpoint.new();
38
+ end
39
+
40
+ def offer(topicList, data)
41
+ @waitpoint.fire([topicList, data]);
42
+ end
43
+ end
44
+
45
+ class ValueTrackerSubscription < Subscription
46
+ attr_reader :value
47
+
48
+ def initialize(topic, qos = 1)
49
+ raise ArgumentError, "Tracking of topic wildcards is prohibited! Topic: #{topic}" if topic =~ /[#\+]/
50
+ super(topic, qos);
51
+
52
+ @value = nil;
53
+
54
+ @callbackList = Array.new();
55
+ end
56
+
57
+ def offer(topicList, data)
58
+ return if data == @value;
59
+
60
+ @callbackList.each do |cb|
61
+ cb.call(data, @value);
62
+ end
63
+ @value = data;
64
+ end
65
+
66
+ def attach(callback)
67
+ @callbackList << callback;
68
+ callback.call(@value, nil) if(@value);
69
+ return callback;
70
+ end
71
+ def detach(callback)
72
+ @callbackList.delete callback;
73
+ end
74
+ end
75
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mqtt-sub_handler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Xasin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-02-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mqtt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.0
27
+ description: A gem to use when the normal ruby mqtt doesn't hit the spot.
28
+ email:
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/mqtt/Waitpoint.rb
34
+ - lib/mqtt/sub_handler.rb
35
+ - lib/mqtt/subscription_classes.rb
36
+ homepage: https://github.com/XasWorks/XasCode/tree/MQTT_GEM/Ruby/MQTT
37
+ licenses:
38
+ - GPL-3.0
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 2.6.14
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Asynchronous, topic-based MQTT gem
60
+ test_files: []