mqtt-sub_handler 0.0.4 → 0.1.0.dev
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 +4 -4
- data/README.md +3 -7
- data/lib/mqtt/sub_handler.rb +57 -49
- data/lib/mqtt/sub_testing.rb +105 -0
- data/lib/mqtt/subscription_classes.rb +2 -2
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 826fa334ef34a9c8b441343ea1ef8b80652ad4df
|
4
|
+
data.tar.gz: 07cfe57e8b9b7e0d355616f60042991104efff4f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c9f4e85523610a599c764980fbbe1258e6641f8074c2f607d99256d7e9984c19be091c92bdd3b3b71c3d51da78e5db423c23167cb41d5d4d7b7c80cc3677507
|
7
|
+
data.tar.gz: fe6f9db5d8bf6b6166898f2f468935f0d555c6a31fddc7beffa56ce89b31c722964c54c41ab8f45daa5422195d54a6d9807978b5553ee5e7787cca27e66b8671
|
data/README.md
CHANGED
@@ -1,11 +1,7 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
*I'm working on documenting the code, so if you can't figure out how to get it running, come back in a few days or make an issue explaining your problem!*
|
7
|
-
|
8
|
-
---
|
2
|
+
### Where're the tests?
|
3
|
+
They're not here yet! But if you know how to set them up, I'd absolutely love to have you contribute.
|
4
|
+
Open an issue about that and we can figure things out!
|
9
5
|
|
10
6
|
# A topic-based, asynchronous MQTT handler
|
11
7
|
That's right, finally there's a *mostly* stable, connection-loss resistant MQTT handler out there that runs fully asynchronously.
|
data/lib/mqtt/sub_handler.rb
CHANGED
@@ -40,10 +40,10 @@ class SubHandler
|
|
40
40
|
return nil unless receivedTopicList.length >= topicPattern.length;
|
41
41
|
|
42
42
|
topicPattern.each_index do |i|
|
43
|
-
if(topicPattern[i] == "+")
|
43
|
+
if(topicPattern[i] == "+")
|
44
44
|
outputTopicList << receivedTopicList[i];
|
45
45
|
|
46
|
-
elsif(topicPattern[i] == "#")
|
46
|
+
elsif(topicPattern[i] == "#")
|
47
47
|
outputTopicList.concat receivedTopicList[i..-1];
|
48
48
|
return outputTopicList;
|
49
49
|
|
@@ -76,7 +76,7 @@ class SubHandler
|
|
76
76
|
def raw_subscribe_to(topic, qos: 1)
|
77
77
|
begin
|
78
78
|
@conChangeMutex.lock
|
79
|
-
if not @connected
|
79
|
+
if not @connected
|
80
80
|
@subscribeQueue << [topic, qos];
|
81
81
|
@conChangeMutex.unlock
|
82
82
|
else
|
@@ -128,7 +128,7 @@ class SubHandler
|
|
128
128
|
# @yieldparam topicList [Array<String>] The wildcard topic branches matched.
|
129
129
|
# @yieldreturn [Boolean] Whether or not the data was sufficient, and capture should be stopped.
|
130
130
|
def wait_for(topic, qos: 1, timeout: nil)
|
131
|
-
unless block_given?
|
131
|
+
unless block_given?
|
132
132
|
raise ArgumentError, "A block for data-processing needs to be passed!"
|
133
133
|
end
|
134
134
|
|
@@ -136,10 +136,10 @@ class SubHandler
|
|
136
136
|
register_subscription(subObject);
|
137
137
|
|
138
138
|
begin
|
139
|
-
Timeout
|
139
|
+
Timeout.timeout(timeout) do
|
140
140
|
loop do
|
141
141
|
return_data = subObject.waitpoint.wait()[1];
|
142
|
-
if yield(return_data[0], return_data[1])
|
142
|
+
if yield(return_data[0], return_data[1])
|
143
143
|
return true;
|
144
144
|
end
|
145
145
|
end
|
@@ -208,7 +208,7 @@ class SubHandler
|
|
208
208
|
|
209
209
|
begin
|
210
210
|
@conChangeMutex.lock
|
211
|
-
if not @connected
|
211
|
+
if not @connected
|
212
212
|
@publishQueue << {topic: topic, data: data, qos: qos, retain: retain} unless qos == 0
|
213
213
|
@conChangeMutex.unlock
|
214
214
|
else
|
@@ -222,10 +222,51 @@ class SubHandler
|
|
222
222
|
end
|
223
223
|
alias publishTo publish_to
|
224
224
|
|
225
|
+
def ensure_clean_start()
|
226
|
+
@mqttWasStartedClean = @mqtt.clean_session
|
227
|
+
if @mqttWasStartedClean
|
228
|
+
begin
|
229
|
+
@mqtt.connect();
|
230
|
+
@mqtt.disconnect();
|
231
|
+
rescue MQTT::Exception
|
232
|
+
sleep 1;
|
233
|
+
retry
|
234
|
+
rescue SocketError, SystemCallError
|
235
|
+
sleep 5
|
236
|
+
retry
|
237
|
+
end
|
238
|
+
@mqtt.clean_session=false;
|
239
|
+
end
|
240
|
+
end
|
241
|
+
private :ensure_clean_start
|
242
|
+
|
243
|
+
def ensure_clean_exit()
|
244
|
+
if(@mqttWasStartedClean)
|
245
|
+
print "Logging out of mqtt server... "
|
246
|
+
begin
|
247
|
+
Timeout.timeout(10) {
|
248
|
+
begin
|
249
|
+
@mqtt.clean_session = true;
|
250
|
+
@mqtt.disconnect();
|
251
|
+
@mqtt.connect();
|
252
|
+
rescue MQTT::Exception, SocketError, SystemCallError
|
253
|
+
sleep 1
|
254
|
+
retry;
|
255
|
+
end
|
256
|
+
}
|
257
|
+
rescue Timeout::Error
|
258
|
+
puts "Timed out, aborting!";
|
259
|
+
else
|
260
|
+
puts "Done."
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
private :ensure_clean_exit
|
265
|
+
|
225
266
|
def mqtt_resub_thread
|
226
267
|
while(true)
|
227
268
|
begin
|
228
|
-
Timeout
|
269
|
+
Timeout.timeout(10) {
|
229
270
|
@mqtt.connect()
|
230
271
|
}
|
231
272
|
@conChangeMutex.synchronize {
|
@@ -270,12 +311,12 @@ class SubHandler
|
|
270
311
|
end
|
271
312
|
def flush_pubqueue()
|
272
313
|
puts "\n";
|
273
|
-
if @publishQueue.empty?
|
314
|
+
if @publishQueue.empty?
|
274
315
|
puts "MQTT buffer empty, continuing."
|
275
316
|
else
|
276
317
|
print "Finishing sending of MQTT messages ... "
|
277
318
|
begin
|
278
|
-
Timeout
|
319
|
+
Timeout.timeout(10) {
|
279
320
|
until @publishQueue.empty? do
|
280
321
|
sleep 0.05;
|
281
322
|
end
|
@@ -299,7 +340,7 @@ class SubHandler
|
|
299
340
|
# mqtt = MQTT::SubHandler.new(MQTT::Client.new("Your.Client.Opts"))
|
300
341
|
def initialize(mqttClient)
|
301
342
|
@callbackList = Array.new();
|
302
|
-
if mqttClient.is_a? String
|
343
|
+
if mqttClient.is_a? String
|
303
344
|
@mqtt = MQTT::Client.new(mqttClient);
|
304
345
|
else
|
305
346
|
@mqtt = mqttClient;
|
@@ -317,52 +358,19 @@ class SubHandler
|
|
317
358
|
@trackerHash = Hash.new();
|
318
359
|
|
319
360
|
@listenerThread = Thread.new do
|
320
|
-
|
321
|
-
|
322
|
-
begin
|
323
|
-
@mqtt.connect();
|
324
|
-
@mqtt.disconnect();
|
325
|
-
rescue MQTT::Exception
|
326
|
-
sleep 1;
|
327
|
-
retry
|
328
|
-
rescue SocketError, SystemCallError
|
329
|
-
sleep 5
|
330
|
-
retry
|
331
|
-
end
|
332
|
-
@mqtt.clean_session=false;
|
333
|
-
end
|
334
|
-
|
335
|
-
mqtt_resub_thread
|
361
|
+
ensure_clean_start();
|
362
|
+
mqtt_resub_thread();
|
336
363
|
end
|
337
364
|
@listenerThread.abort_on_exception = true;
|
338
365
|
|
339
366
|
at_exit {
|
340
|
-
flush_pubqueue
|
367
|
+
flush_pubqueue();
|
341
368
|
@listenerThread.kill();
|
342
|
-
|
343
|
-
if(@mqttWasStartedClean) then
|
344
|
-
print "Logging out of mqtt server... "
|
345
|
-
begin
|
346
|
-
Timeout::timeout(10) {
|
347
|
-
begin
|
348
|
-
@mqtt.clean_session = true;
|
349
|
-
@mqtt.disconnect();
|
350
|
-
@mqtt.connect();
|
351
|
-
rescue MQTT::Exception, SocketError, SystemCallError
|
352
|
-
sleep 1
|
353
|
-
retry;
|
354
|
-
end
|
355
|
-
}
|
356
|
-
rescue Timeout::Error
|
357
|
-
puts "Timed out, aborting!";
|
358
|
-
else
|
359
|
-
puts "Done."
|
360
|
-
end
|
361
|
-
end
|
369
|
+
ensure_clean_exit();
|
362
370
|
}
|
363
371
|
|
364
372
|
begin
|
365
|
-
Timeout
|
373
|
+
Timeout.timeout(10) {
|
366
374
|
until(@connected) do sleep 0.1; end
|
367
375
|
}
|
368
376
|
rescue Timeout::Error
|
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
require "mqtt/sub_handler"
|
3
|
+
|
4
|
+
module MQTT
|
5
|
+
module Testing
|
6
|
+
# This class is meant purely for testing.
|
7
|
+
# It completely removes the need for a external MQTT broker, and captures errors.
|
8
|
+
# Message processing can be done step-by-step, for better analysis of errors.
|
9
|
+
# Its interface is identical to the main class, making it indistinguishable.
|
10
|
+
class SubHandler < MQTT::SubHandler
|
11
|
+
attr_reader :message_log
|
12
|
+
attr_reader :publish_queue
|
13
|
+
attr_accessor :retained_topics
|
14
|
+
|
15
|
+
def call_interested(topic, data)
|
16
|
+
@callbackList.each do |h|
|
17
|
+
tMatch = SubHandler.getTopicMatch(topic, h.topic_split);
|
18
|
+
if tMatch then
|
19
|
+
begin
|
20
|
+
h.offer(tMatch, data)
|
21
|
+
rescue StandardError => e
|
22
|
+
if(@error_handler)
|
23
|
+
@error_handler.call(e);
|
24
|
+
else
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
return
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
private :call_interested
|
33
|
+
|
34
|
+
def raw_subscribe_to(topic, qos: nil)
|
35
|
+
if(@retained_topics[topic]) then
|
36
|
+
publish_to(topic, @retained_topics[topic])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
private :raw_subscribe_to
|
40
|
+
|
41
|
+
# Publish a message to topic.
|
42
|
+
# @param topic [String] The topic to push to.
|
43
|
+
# @param data [String] The data to be transmitted.
|
44
|
+
# @note The published data is not immediately processed.
|
45
|
+
# Use process_message or process_all
|
46
|
+
def publish_to(topic, data, qos: nil, retain: false)
|
47
|
+
@publish_queue << [topic, data];
|
48
|
+
@message_log[topic] << data;
|
49
|
+
@retained_topics[topic] = data if retain;
|
50
|
+
end
|
51
|
+
|
52
|
+
# Process a single MQTT message in queue, recording errors etc.
|
53
|
+
def process_message
|
54
|
+
return if @publish_queue.empty?
|
55
|
+
packet = @publish_queue.pop
|
56
|
+
call_interested(packet[0], packet[1]);
|
57
|
+
end
|
58
|
+
|
59
|
+
# Process all messages until the queue is empty.
|
60
|
+
# Do remember that the callbacks can publish new data, which then gets processed again!
|
61
|
+
# @param max_loops [Integer] Amount of loops to do before aborting
|
62
|
+
# @param error_on_loop [Boolean] Raise an error if too many loops happened?
|
63
|
+
def process_all(max_loops: 500, error_on_loop: true)
|
64
|
+
until @publish_queue.empty?
|
65
|
+
process_message
|
66
|
+
max_loops -= 1;
|
67
|
+
if(max_loops == 0)
|
68
|
+
if(error_on_loop)
|
69
|
+
raise RuntimeError, "MQTT Loop recursion detected!"
|
70
|
+
end
|
71
|
+
return
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Prepare the code for the next test by cleaning out all queues.
|
77
|
+
# The list of callbacks is not affected
|
78
|
+
# @param retained [Boolean, Hash<String, String>] Either a bool whether or not to clear retained messages,
|
79
|
+
# or a Hash with Topic-Keys containing String-Data to use.
|
80
|
+
def prepare(retained: true)
|
81
|
+
@publish_queue.clear();
|
82
|
+
if(retained.is_a? Hash)
|
83
|
+
@retained_topics = retained.clone
|
84
|
+
@retained_topics.each do |topic, data|
|
85
|
+
publish_to(topic, data);
|
86
|
+
end
|
87
|
+
elsif(retained)
|
88
|
+
@retained_topics = Hash.new();
|
89
|
+
end
|
90
|
+
@message_log.clear()
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize()
|
94
|
+
@callbackList = Array.new();
|
95
|
+
@retained_topics = Hash.new();
|
96
|
+
@publish_queue = Queue.new();
|
97
|
+
@message_log = Hash.new() do |h, key| h = Array.new() end;
|
98
|
+
end
|
99
|
+
|
100
|
+
def on_error(&handler)
|
101
|
+
@error_handler = handler;
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -9,7 +9,7 @@ module MQTT
|
|
9
9
|
attr_reader :qos
|
10
10
|
attr_reader :topic_split
|
11
11
|
|
12
|
-
def initialize(topic,
|
12
|
+
def initialize(topic, _qos)
|
13
13
|
@topic = topic;
|
14
14
|
@topic_split = SubHandler.getTopicSplit(topic);
|
15
15
|
|
@@ -56,7 +56,7 @@ module MQTT
|
|
56
56
|
@callbackList = Array.new();
|
57
57
|
end
|
58
58
|
|
59
|
-
def offer(
|
59
|
+
def offer(_topicList, data)
|
60
60
|
return if data == @value;
|
61
61
|
oldValue = @value;
|
62
62
|
@value = data;
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mqtt-sub_handler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.1.0.dev
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xasin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mqtt
|
@@ -34,6 +34,7 @@ files:
|
|
34
34
|
- README.md
|
35
35
|
- lib/mqtt/Waitpoint.rb
|
36
36
|
- lib/mqtt/sub_handler.rb
|
37
|
+
- lib/mqtt/sub_testing.rb
|
37
38
|
- lib/mqtt/subscription_classes.rb
|
38
39
|
homepage: https://github.com/XasWorks/XasCode/tree/MQTT_GEM/Ruby/MQTT
|
39
40
|
licenses:
|
@@ -50,9 +51,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
51
|
version: '0'
|
51
52
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
53
|
requirements:
|
53
|
-
- - "
|
54
|
+
- - ">"
|
54
55
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
56
|
+
version: 1.3.1
|
56
57
|
requirements: []
|
57
58
|
rubyforge_project:
|
58
59
|
rubygems_version: 2.6.14.1
|