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 +7 -0
- data/lib/mqtt/Waitpoint.rb +44 -0
- data/lib/mqtt/sub_handler.rb +289 -0
- data/lib/mqtt/subscription_classes.rb +75 -0
- metadata +60 -0
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: []
|