mqtt-sub_handler 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.
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: []