mqtt-sub_handler 0.1.6.2 → 0.1.6.4
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/lib/mqtt/base_handler.rb +350 -0
- data/lib/mqtt/sub_handler.rb +7 -309
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d3eef659ff2c5da99d386d05fc145c328d7eea5b2057d30fa7f1ec43bda7748c
|
|
4
|
+
data.tar.gz: 69559fc70301f60f25ea322731b936738eb7d55609a8c817fcedf161e3640590
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1693df3d20154bb9a283cd75f0f3f82411173c7716daa89f84c89a0497ef36fa92965827927baa32ec1ead5c6697e87f3270c10099b7fd3815695be06af730f0
|
|
7
|
+
data.tar.gz: cc4fab89804258ffdbec5d89a11a94426c264b2866f10780cfff4aab3422cb78ca031b784e0e0200b20815f3ffc27934740b9250c8a0ef1283779bcbc05a0206
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
|
|
2
|
+
require 'timeout'
|
|
3
|
+
require 'mqtt'
|
|
4
|
+
require 'colorize'
|
|
5
|
+
|
|
6
|
+
require 'xasin_logger'
|
|
7
|
+
|
|
8
|
+
module MQTT
|
|
9
|
+
class BaseHandler
|
|
10
|
+
include XasLogger::Mix
|
|
11
|
+
|
|
12
|
+
# Split a Topic into a Topic-Array
|
|
13
|
+
# @param topicName [String] The string topic which to split
|
|
14
|
+
# @return [Array<String>] A list of individual topic-branches
|
|
15
|
+
# @note This function is mainly used for background processing.
|
|
16
|
+
def self.get_topic_split(topicName)
|
|
17
|
+
return topicName.scan(/[^\/]+/);
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Match a topic string to a topic pattern
|
|
21
|
+
# @param receivedTopicString [String] The string (as
|
|
22
|
+
# returned by MQTT.get) to compare
|
|
23
|
+
# @param topicPattern [Array<String>] The Topic-Array (as
|
|
24
|
+
# returned by .get_topic_split) to compare against
|
|
25
|
+
# @return [nil, Array<String>] Nil if no match was found.
|
|
26
|
+
# An Array of matched wildcard topic branches (can be empty) when
|
|
27
|
+
# successfully matched
|
|
28
|
+
# @note (see .get_topic_split)
|
|
29
|
+
def self.getTopicMatch(receivedTopicString, topicPattern)
|
|
30
|
+
receivedTopicList = get_topic_split receivedTopicString;
|
|
31
|
+
|
|
32
|
+
outputTopicList = Array.new();
|
|
33
|
+
|
|
34
|
+
return nil unless receivedTopicList.length >= topicPattern.length;
|
|
35
|
+
|
|
36
|
+
topicPattern.each_index do |i|
|
|
37
|
+
if(topicPattern[i] == "+")
|
|
38
|
+
outputTopicList << receivedTopicList[i];
|
|
39
|
+
|
|
40
|
+
elsif(topicPattern[i] == "#")
|
|
41
|
+
outputTopicList.concat receivedTopicList[i..-1];
|
|
42
|
+
return outputTopicList;
|
|
43
|
+
|
|
44
|
+
elsif topicPattern[i] != receivedTopicList[i];
|
|
45
|
+
return nil;
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
return outputTopicList if topicPattern.length == receivedTopicList.length;
|
|
51
|
+
return nil;
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Call all existing callbacks whose topic-list matches `topic`
|
|
55
|
+
def call_interested(topic, data)
|
|
56
|
+
topicHasReceivers = false;
|
|
57
|
+
@callbackList.each do |h|
|
|
58
|
+
tMatch = BaseHandler.getTopicMatch(topic, h.topic_split);
|
|
59
|
+
if tMatch
|
|
60
|
+
begin
|
|
61
|
+
Timeout.timeout(10) {
|
|
62
|
+
h.offer(tMatch, data)
|
|
63
|
+
}
|
|
64
|
+
rescue Timeout::Error
|
|
65
|
+
x_logf("Timeout on callback #{h}");
|
|
66
|
+
rescue => e
|
|
67
|
+
x_logf("Uncaught error on #{h}");
|
|
68
|
+
x_logf(e);
|
|
69
|
+
end
|
|
70
|
+
topicHasReceivers = true;
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@mqtt.unsubscribe(topic) unless topicHasReceivers;
|
|
75
|
+
end
|
|
76
|
+
private :call_interested
|
|
77
|
+
|
|
78
|
+
def queue_packet(data)
|
|
79
|
+
return if @destroying
|
|
80
|
+
|
|
81
|
+
x_logd("Queueing packet #{data.to_json}");
|
|
82
|
+
@packetQueueMutex.synchronize {
|
|
83
|
+
@packetQueue << data;
|
|
84
|
+
if(@packetQueue.size == 999)
|
|
85
|
+
x_loge("Packet queue congested, dropping packets!");
|
|
86
|
+
end
|
|
87
|
+
if(@packetQueue.size > 1000)
|
|
88
|
+
@packetQueue.shift
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@publisherThread.run() if @publisherThreadWaiting;
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
private :queue_packet
|
|
95
|
+
|
|
96
|
+
# @!group Custom subscription handling
|
|
97
|
+
|
|
98
|
+
# Unregister a subscription. Removes it from the callback list and
|
|
99
|
+
# unsubscribes from the topic if no other subscriptions for it are present.
|
|
100
|
+
# @param subObject [MQTT::Subscriptions::Subscription]
|
|
101
|
+
# The subscription-object to remove
|
|
102
|
+
# @return void
|
|
103
|
+
def unregister_subscription(subObject)
|
|
104
|
+
raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscriptions::Subscription
|
|
105
|
+
return unless @callbackList.include? subObject;
|
|
106
|
+
|
|
107
|
+
queue_packet({type: :unsub, topic: subObject.topic});
|
|
108
|
+
@callbackList.delete(subObject);
|
|
109
|
+
end
|
|
110
|
+
# Register a custom subscription, and send a subscription message to the server.
|
|
111
|
+
# @param subObject [MQTT::Subscriptions::Subscription]
|
|
112
|
+
# An instance of a MQTT Subscription object
|
|
113
|
+
# @return void
|
|
114
|
+
def register_subscription(subObject)
|
|
115
|
+
raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscriptions::Subscription
|
|
116
|
+
return if @callbackList.include? subObject;
|
|
117
|
+
|
|
118
|
+
@callbackList << subObject;
|
|
119
|
+
queue_packet({type: :sub, topic: subObject.topic, qos: subObject.qos});
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @!endgroup
|
|
123
|
+
|
|
124
|
+
def ensure_clean_start()
|
|
125
|
+
@mqttWasStartedClean = @mqtt.clean_session
|
|
126
|
+
if @mqttWasStartedClean
|
|
127
|
+
begin
|
|
128
|
+
@mqtt.connect();
|
|
129
|
+
@mqtt.disconnect();
|
|
130
|
+
rescue MQTT::Exception
|
|
131
|
+
sleep 1;
|
|
132
|
+
retry
|
|
133
|
+
rescue SocketError, SystemCallError
|
|
134
|
+
sleep 5
|
|
135
|
+
retry
|
|
136
|
+
end
|
|
137
|
+
@mqtt.clean_session=false;
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
private :ensure_clean_start
|
|
141
|
+
|
|
142
|
+
def ensure_clean_exit()
|
|
143
|
+
if(@mqttWasStartedClean)
|
|
144
|
+
x_logi("Logging out.")
|
|
145
|
+
begin
|
|
146
|
+
Timeout.timeout(3) {
|
|
147
|
+
begin
|
|
148
|
+
@mqtt.clean_session = true;
|
|
149
|
+
@mqtt.disconnect();
|
|
150
|
+
@mqtt.connect();
|
|
151
|
+
rescue MQTT::Exception, SocketError, SystemCallError
|
|
152
|
+
sleep 0.3
|
|
153
|
+
retry;
|
|
154
|
+
end
|
|
155
|
+
}
|
|
156
|
+
rescue Timeout::Error
|
|
157
|
+
x_loge("Timed out, aborting!");
|
|
158
|
+
else
|
|
159
|
+
x_logi("Done");
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
private :ensure_clean_exit
|
|
164
|
+
|
|
165
|
+
def mqtt_push_thread
|
|
166
|
+
loop do
|
|
167
|
+
@packetQueueMutex.synchronize {
|
|
168
|
+
@publisherThreadWaiting = true;
|
|
169
|
+
}
|
|
170
|
+
x_logd("Push thread stopping")
|
|
171
|
+
sleep 1
|
|
172
|
+
x_logd("Push thread active")
|
|
173
|
+
@packetQueueMutex.synchronize {
|
|
174
|
+
@publisherThreadWaiting = false;
|
|
175
|
+
}
|
|
176
|
+
break if @destroying
|
|
177
|
+
|
|
178
|
+
next unless @connected
|
|
179
|
+
|
|
180
|
+
begin
|
|
181
|
+
until @packetQueue.empty? do
|
|
182
|
+
h = nil;
|
|
183
|
+
@packetQueueMutex.synchronize {
|
|
184
|
+
h = @packetQueue[0];
|
|
185
|
+
}
|
|
186
|
+
Timeout.timeout(3) {
|
|
187
|
+
if(h[:type] == :sub)
|
|
188
|
+
@mqtt.subscribe(h[:topic] => h[:qos]);
|
|
189
|
+
elsif(h[:type] == :pub)
|
|
190
|
+
@mqtt.publish(h[:topic], h[:data], h[:retain], h[:qos]);
|
|
191
|
+
end
|
|
192
|
+
}
|
|
193
|
+
@packetQueueMutex.synchronize {
|
|
194
|
+
@packetQueue.shift();
|
|
195
|
+
}
|
|
196
|
+
end
|
|
197
|
+
rescue MQTT::Exception, SocketError, SystemCallError, Timeout::Error => e
|
|
198
|
+
x_loge("Push error!");
|
|
199
|
+
x_loge(e.inspet);
|
|
200
|
+
|
|
201
|
+
sleep 0.5
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
x_logd("Push thread exited!");
|
|
206
|
+
end
|
|
207
|
+
private :mqtt_push_thread
|
|
208
|
+
|
|
209
|
+
def mqtt_resub_thread
|
|
210
|
+
loop do
|
|
211
|
+
begin
|
|
212
|
+
return if @destroying
|
|
213
|
+
|
|
214
|
+
x_logw("Trying to reconnect...");
|
|
215
|
+
Timeout.timeout(4) {
|
|
216
|
+
@mqtt.connect()
|
|
217
|
+
}
|
|
218
|
+
x_logi("Connected!");
|
|
219
|
+
@conChangeMutex.synchronize {
|
|
220
|
+
@connected = true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@packetQueueMutex.synchronize {
|
|
224
|
+
@publisherThread.run() if (@publisherThread && @publisherThreadWaiting)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
x_logd("Sub thread reading...");
|
|
228
|
+
@mqtt.get do |topic, message|
|
|
229
|
+
call_interested(topic, message);
|
|
230
|
+
end
|
|
231
|
+
rescue MQTT::Exception, Timeout::Error, SocketError, SystemCallError
|
|
232
|
+
x_loge("Disconnected!") if @connected
|
|
233
|
+
@connected = false;
|
|
234
|
+
|
|
235
|
+
@conChangeMutex.unlock if @conChangeMutex.owned?
|
|
236
|
+
@mqtt.clean_session = false;
|
|
237
|
+
sleep 2
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
x_logd("Sub thread exited");
|
|
242
|
+
end
|
|
243
|
+
private :mqtt_resub_thread
|
|
244
|
+
|
|
245
|
+
# Pause the main thread and wait for messages.
|
|
246
|
+
# This is mainly useful when the code has set everything up, but doesn't just want to end.
|
|
247
|
+
# "INT" is trapped, ensuring a smooth exit on Ctrl-C
|
|
248
|
+
def lockAndListen()
|
|
249
|
+
Signal.trap("INT") {
|
|
250
|
+
exit 0
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
puts "Main thread paused."
|
|
254
|
+
Thread.stop();
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def destroy!()
|
|
258
|
+
return if @destroying
|
|
259
|
+
@destroying = true;
|
|
260
|
+
|
|
261
|
+
unless @packetQueue.empty?
|
|
262
|
+
x_logd "Finishing sending of MQTT messages ... "
|
|
263
|
+
@publisherThread.run() if @publisherThreadWaiting
|
|
264
|
+
begin
|
|
265
|
+
Timeout.timeout(4) {
|
|
266
|
+
until @packetQueue.empty? do
|
|
267
|
+
sleep 0.05;
|
|
268
|
+
end
|
|
269
|
+
}
|
|
270
|
+
rescue Timeout::Error
|
|
271
|
+
x_logw "Publishes did not complete";
|
|
272
|
+
else
|
|
273
|
+
x_logd "Publish clean finished"
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
@publisherThread.run();
|
|
278
|
+
@publisherThread.join();
|
|
279
|
+
@listenerThread.kill();
|
|
280
|
+
|
|
281
|
+
@mqtt.disconnect() if @connected
|
|
282
|
+
|
|
283
|
+
ensure_clean_exit();
|
|
284
|
+
|
|
285
|
+
x_logi("Fully disconnected!");
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Initialize a new MQTT::SubHandler
|
|
289
|
+
# The handler immediately connects to the server, and begins receciving and sending.
|
|
290
|
+
# @param mqttClient [String, MQTT::Client] Either a URI to connect to, or a MQTT::Client
|
|
291
|
+
# The URI can be of the form "mqtts://Password@User:URL:port".
|
|
292
|
+
# The MQTT client instance can be fully configured, as specified by the MQTT Gem. It must *not* already be connected!
|
|
293
|
+
# @param jsonify [Boolean] Should Hashes and Arrays input into publish_to be converted to JSON?
|
|
294
|
+
# This can be useful to have one less .to_json call. Default is true.
|
|
295
|
+
# @example Starting the handler
|
|
296
|
+
# mqtt = MQTT::SubHandler.new('mqtt.eclipse.org');
|
|
297
|
+
# mqtt = MQTT::SubHandler.new(MQTT::Client.new("Your.Client.Opts"))
|
|
298
|
+
def initialize(mqttClient, logger: nil)
|
|
299
|
+
@callbackList = Array.new();
|
|
300
|
+
if mqttClient.is_a? String
|
|
301
|
+
@mqtt = MQTT::Client.new(mqttClient);
|
|
302
|
+
@mqtt.clean_session = false;
|
|
303
|
+
else
|
|
304
|
+
@mqtt = mqttClient;
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
init_x_log("MQTT #{@mqtt.host}", logger);
|
|
308
|
+
self.log_level = Logger::INFO;
|
|
309
|
+
|
|
310
|
+
@conChangeMutex = Mutex.new();
|
|
311
|
+
@connected = false;
|
|
312
|
+
|
|
313
|
+
@mqtt.client_id ||= MQTT::Client.generate_client_id("MQTT_Sub_", 8);
|
|
314
|
+
|
|
315
|
+
@packetQueue = Array.new();
|
|
316
|
+
@packetQueueMutex = Mutex.new();
|
|
317
|
+
|
|
318
|
+
@publisherThreadWaiting = false;
|
|
319
|
+
|
|
320
|
+
@subscribedTopics = Hash.new();
|
|
321
|
+
|
|
322
|
+
@trackerHash = Hash.new();
|
|
323
|
+
|
|
324
|
+
@listenerThread = Thread.new do
|
|
325
|
+
ensure_clean_start();
|
|
326
|
+
mqtt_resub_thread();
|
|
327
|
+
end
|
|
328
|
+
@listenerThread.abort_on_exception = true;
|
|
329
|
+
|
|
330
|
+
begin
|
|
331
|
+
Timeout.timeout(5) {
|
|
332
|
+
until(@connected)
|
|
333
|
+
sleep 0.1;
|
|
334
|
+
end
|
|
335
|
+
}
|
|
336
|
+
rescue Timeout::Error
|
|
337
|
+
x_loge("Broker did not connect!");
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
@publisherThread = Thread.new do
|
|
341
|
+
mqtt_push_thread();
|
|
342
|
+
end
|
|
343
|
+
@publisherThread.abort_on_exception = true;
|
|
344
|
+
|
|
345
|
+
at_exit {
|
|
346
|
+
destroy!()
|
|
347
|
+
}
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
data/lib/mqtt/sub_handler.rb
CHANGED
|
@@ -1,129 +1,24 @@
|
|
|
1
1
|
|
|
2
|
-
require 'timeout'
|
|
3
|
-
require 'mqtt'
|
|
4
|
-
require 'json'
|
|
5
2
|
|
|
6
|
-
require '
|
|
3
|
+
require 'json'
|
|
7
4
|
|
|
8
5
|
require_relative 'subscription_classes.rb'
|
|
6
|
+
require_relative 'base_handler.rb'
|
|
9
7
|
|
|
10
8
|
# @author Xasin
|
|
11
9
|
module MQTT
|
|
12
10
|
# A shortcut-function to quickly connect to the public Eclipse MQTT Broker.
|
|
13
|
-
# @return [MQTT::SubHandler] Sub-Handler connected to `
|
|
11
|
+
# @return [MQTT::SubHandler] Sub-Handler connected to `mqtt.eclipse.org`
|
|
14
12
|
def self.Eclipse()
|
|
15
|
-
@EclipseMQTT ||= SubHandler.new('
|
|
13
|
+
@EclipseMQTT ||= SubHandler.new('mqtt.eclipse.org');
|
|
16
14
|
return @EclipseMQTT;
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
|
|
20
|
-
class SubHandler
|
|
18
|
+
class SubHandler < BaseHandler
|
|
21
19
|
# Whether or not hashes and arrays should be converted to JSON when sending
|
|
22
20
|
attr_accessor :jsonifyHashes
|
|
23
21
|
|
|
24
|
-
# Split a Topic into a Topic-Array
|
|
25
|
-
# @param topicName [String] The string topic which to split
|
|
26
|
-
# @return [Array<String>] A list of individual topic-branches
|
|
27
|
-
# @note This function is mainly used for background processing.
|
|
28
|
-
def self.get_topic_split(topicName)
|
|
29
|
-
return topicName.scan(/[^\/]+/);
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Match a topic string to a topic pattern
|
|
33
|
-
# @param receivedTopicString [String] The string (as
|
|
34
|
-
# returned by MQTT.get) to compare
|
|
35
|
-
# @param topicPattern [Array<String>] The Topic-Array (as
|
|
36
|
-
# returned by .get_topic_split) to compare against
|
|
37
|
-
# @return [nil, Array<String>] Nil if no match was found.
|
|
38
|
-
# An Array of matched wildcard topic branches (can be empty) when
|
|
39
|
-
# successfully matched
|
|
40
|
-
# @note (see .get_topic_split)
|
|
41
|
-
def self.getTopicMatch(receivedTopicString, topicPattern)
|
|
42
|
-
receivedTopicList = get_topic_split receivedTopicString;
|
|
43
|
-
|
|
44
|
-
outputTopicList = Array.new();
|
|
45
|
-
|
|
46
|
-
return nil unless receivedTopicList.length >= topicPattern.length;
|
|
47
|
-
|
|
48
|
-
topicPattern.each_index do |i|
|
|
49
|
-
if(topicPattern[i] == "+")
|
|
50
|
-
outputTopicList << receivedTopicList[i];
|
|
51
|
-
|
|
52
|
-
elsif(topicPattern[i] == "#")
|
|
53
|
-
outputTopicList.concat receivedTopicList[i..-1];
|
|
54
|
-
return outputTopicList;
|
|
55
|
-
|
|
56
|
-
elsif topicPattern[i] != receivedTopicList[i];
|
|
57
|
-
return nil;
|
|
58
|
-
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
return outputTopicList if topicPattern.length == receivedTopicList.length;
|
|
63
|
-
return nil;
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Call all existing callbacks whose topic-list matches `topic`
|
|
67
|
-
def call_interested(topic, data)
|
|
68
|
-
topicHasReceivers = false;
|
|
69
|
-
@callbackList.each do |h|
|
|
70
|
-
tMatch = SubHandler.getTopicMatch(topic, h.topic_split);
|
|
71
|
-
if tMatch
|
|
72
|
-
begin
|
|
73
|
-
Timeout.timeout(5) {
|
|
74
|
-
h.offer(tMatch, data)
|
|
75
|
-
}
|
|
76
|
-
rescue Timeout::Error
|
|
77
|
-
STDERR.puts "MQTT: Callback Timeout #{h}".red
|
|
78
|
-
end
|
|
79
|
-
topicHasReceivers = true;
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
@mqtt.unsubscribe(topic) unless topicHasReceivers;
|
|
84
|
-
end
|
|
85
|
-
private :call_interested
|
|
86
|
-
|
|
87
|
-
def queue_packet(data)
|
|
88
|
-
@packetQueueMutex.synchronize {
|
|
89
|
-
@packetQueue << data;
|
|
90
|
-
@packetQueue.shift if @packetQueue.size > 100
|
|
91
|
-
|
|
92
|
-
@publisherThread.run() if @publisherThreadWaiting;
|
|
93
|
-
}
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Handle sending a subscription-message to the server
|
|
97
|
-
def raw_subscribe_to(topic, qos: 1)
|
|
98
|
-
queue_packet({topic: topic, qos: qos, type: :sub});
|
|
99
|
-
end
|
|
100
|
-
private :raw_subscribe_to
|
|
101
|
-
|
|
102
|
-
# @!group Custom subscription handling
|
|
103
|
-
|
|
104
|
-
# Unregister a subscription. Removes it from the callback list and
|
|
105
|
-
# unsubscribes from the topic if no other subscriptions for it are present.
|
|
106
|
-
# @param subObject [MQTT::Subscriptions::Subscription]
|
|
107
|
-
# The subscription-object to remove
|
|
108
|
-
# @return void
|
|
109
|
-
def unregister_subscription(subObject)
|
|
110
|
-
raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscriptions::Subscription
|
|
111
|
-
return unless @callbackList.include? subObject;
|
|
112
|
-
|
|
113
|
-
@callbackList.delete(subObject);
|
|
114
|
-
end
|
|
115
|
-
# Register a custom subscription, and send a subscription message to the server.
|
|
116
|
-
# @param subObject [MQTT::Subscriptions::Subscription]
|
|
117
|
-
# An instance of a MQTT Subscription object
|
|
118
|
-
# @return void
|
|
119
|
-
def register_subscription(subObject)
|
|
120
|
-
raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscriptions::Subscription
|
|
121
|
-
return if @callbackList.include? subObject;
|
|
122
|
-
|
|
123
|
-
@callbackList << subObject;
|
|
124
|
-
raw_subscribe_to(subObject.topic, qos: subObject.qos);
|
|
125
|
-
end
|
|
126
|
-
|
|
127
22
|
# @!group Subscribing
|
|
128
23
|
|
|
129
24
|
# Synchronously wait for data.
|
|
@@ -221,8 +116,7 @@ class SubHandler
|
|
|
221
116
|
|
|
222
117
|
if(qos > 1)
|
|
223
118
|
qos = 1
|
|
224
|
-
|
|
225
|
-
|
|
119
|
+
x_logw("push with QOS > 1 was attempted, this is not supported yet!") unless $MQTTPubQOSWarned
|
|
226
120
|
$MQTTPubQOSWarned = true;
|
|
227
121
|
end
|
|
228
122
|
|
|
@@ -230,206 +124,10 @@ class SubHandler
|
|
|
230
124
|
end
|
|
231
125
|
alias publishTo publish_to
|
|
232
126
|
|
|
233
|
-
def ensure_clean_start()
|
|
234
|
-
@mqttWasStartedClean = @mqtt.clean_session
|
|
235
|
-
if @mqttWasStartedClean
|
|
236
|
-
begin
|
|
237
|
-
@mqtt.connect();
|
|
238
|
-
@mqtt.disconnect();
|
|
239
|
-
rescue MQTT::Exception
|
|
240
|
-
sleep 1;
|
|
241
|
-
retry
|
|
242
|
-
rescue SocketError, SystemCallError
|
|
243
|
-
sleep 5
|
|
244
|
-
retry
|
|
245
|
-
end
|
|
246
|
-
@mqtt.clean_session=false;
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
private :ensure_clean_start
|
|
250
|
-
|
|
251
|
-
def ensure_clean_exit()
|
|
252
|
-
if(@mqttWasStartedClean)
|
|
253
|
-
print "Logging out of mqtt server... "
|
|
254
|
-
begin
|
|
255
|
-
Timeout.timeout(3) {
|
|
256
|
-
begin
|
|
257
|
-
@mqtt.clean_session = true;
|
|
258
|
-
@mqtt.disconnect();
|
|
259
|
-
@mqtt.connect();
|
|
260
|
-
rescue MQTT::Exception, SocketError, SystemCallError
|
|
261
|
-
sleep 0.3
|
|
262
|
-
retry;
|
|
263
|
-
end
|
|
264
|
-
}
|
|
265
|
-
rescue Timeout::Error
|
|
266
|
-
puts "Timed out, aborting!";
|
|
267
|
-
else
|
|
268
|
-
puts "Done."
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
private :ensure_clean_exit
|
|
273
|
-
|
|
274
|
-
def mqtt_push_thread
|
|
275
|
-
loop do
|
|
276
|
-
@packetQueueMutex.synchronize {
|
|
277
|
-
@publisherThreadWaiting = true;
|
|
278
|
-
}
|
|
279
|
-
Thread.stop();
|
|
280
|
-
@packetQueueMutex.synchronize {
|
|
281
|
-
@publisherThreadWaiting = false;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
next unless @connected
|
|
285
|
-
|
|
286
|
-
begin
|
|
287
|
-
until @packetQueue.empty? do
|
|
288
|
-
h = nil;
|
|
289
|
-
@packetQueueMutex.synchronize {
|
|
290
|
-
h = @packetQueue[0];
|
|
291
|
-
}
|
|
292
|
-
Timeout.timeout(3) {
|
|
293
|
-
if(h[:type] == :sub)
|
|
294
|
-
@mqtt.subscribe(h[:topic] => h[:qos]);
|
|
295
|
-
elsif(h[:type] == :pub)
|
|
296
|
-
@mqtt.publish(h[:topic], h[:data], h[:retain], h[:qos]);
|
|
297
|
-
end
|
|
298
|
-
}
|
|
299
|
-
@packetQueueMutex.synchronize {
|
|
300
|
-
@packetQueue.shift();
|
|
301
|
-
}
|
|
302
|
-
end
|
|
303
|
-
rescue MQTT::Exception, SocketError, SystemCallError, Timeout::Error => e
|
|
304
|
-
STDERR.puts("MQTT: #{@mqtt.host} push error, disconnecting!".red) if @connected
|
|
305
|
-
STDERR.puts(e.inspect);
|
|
306
|
-
|
|
307
|
-
sleep 1
|
|
308
|
-
end
|
|
309
|
-
end
|
|
310
|
-
end
|
|
311
|
-
private :mqtt_push_thread
|
|
312
|
-
|
|
313
|
-
def mqtt_resub_thread
|
|
314
|
-
loop do
|
|
315
|
-
begin
|
|
316
|
-
STDERR.puts("MQTT: #{@mqtt.host} trying reconnect...".yellow)
|
|
317
|
-
Timeout.timeout(4) {
|
|
318
|
-
@mqtt.connect()
|
|
319
|
-
}
|
|
320
|
-
STDERR.puts("MQTT: #{@mqtt.host} connected!".green)
|
|
321
|
-
@conChangeMutex.synchronize {
|
|
322
|
-
@connected = true;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
@packetQueueMutex.synchronize {
|
|
326
|
-
@publisherThread.run() if (@publisherThread && @publisherThreadWaiting)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
@mqtt.get do |topic, message|
|
|
330
|
-
call_interested(topic, message);
|
|
331
|
-
end
|
|
332
|
-
rescue MQTT::Exception, Timeout::Error, SocketError, SystemCallError
|
|
333
|
-
STDERR.puts("MQTT: #{@mqtt.host} disconnected!".red) if @connected
|
|
334
|
-
@connected = false;
|
|
335
|
-
|
|
336
|
-
@conChangeMutex.unlock if @conChangeMutex.owned?
|
|
337
|
-
@mqtt.clean_session = false;
|
|
338
|
-
sleep 2
|
|
339
|
-
end
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
private :mqtt_resub_thread
|
|
343
|
-
|
|
344
|
-
# Pause the main thread and wait for messages.
|
|
345
|
-
# This is mainly useful when the code has set everything up, but doesn't just want to end.
|
|
346
|
-
# "INT" is trapped, ensuring a smooth exit on Ctrl-C
|
|
347
|
-
def lockAndListen()
|
|
348
|
-
Signal.trap("INT") {
|
|
349
|
-
exit 0
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
puts "Main thread paused."
|
|
353
|
-
Thread.stop();
|
|
354
|
-
end
|
|
355
|
-
def flush_pubqueue()
|
|
356
|
-
unless @packetQueue.empty?
|
|
357
|
-
print "Finishing sending of MQTT messages ... "
|
|
358
|
-
begin
|
|
359
|
-
Timeout.timeout(4) {
|
|
360
|
-
until @packetQueue.empty? do
|
|
361
|
-
sleep 0.05;
|
|
362
|
-
end
|
|
363
|
-
}
|
|
364
|
-
rescue Timeout::Error
|
|
365
|
-
puts "Timed out, aborting."
|
|
366
|
-
else
|
|
367
|
-
puts "Done."
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
end
|
|
371
|
-
private :flush_pubqueue
|
|
372
|
-
|
|
373
|
-
# Initialize a new MQTT::SubHandler
|
|
374
|
-
# The handler immediately connects to the server, and begins receciving and sending.
|
|
375
|
-
# @param mqttClient [String, MQTT::Client] Either a URI to connect to, or a MQTT::Client
|
|
376
|
-
# The URI can be of the form "mqtts://Password@User:URL:port".
|
|
377
|
-
# The MQTT client instance can be fully configured, as specified by the MQTT Gem. It must *not* already be connected!
|
|
378
|
-
# @param jsonify [Boolean] Should Hashes and Arrays input into publish_to be converted to JSON?
|
|
379
|
-
# This can be useful to have one less .to_json call. Default is true.
|
|
380
|
-
# @example Starting the handler
|
|
381
|
-
# mqtt = MQTT::SubHandler.new('iot.eclipse.org');
|
|
382
|
-
# mqtt = MQTT::SubHandler.new(MQTT::Client.new("Your.Client.Opts"))
|
|
383
127
|
def initialize(mqttClient, jsonify: true)
|
|
384
|
-
|
|
385
|
-
if mqttClient.is_a? String
|
|
386
|
-
@mqtt = MQTT::Client.new(mqttClient);
|
|
387
|
-
else
|
|
388
|
-
@mqtt = mqttClient;
|
|
389
|
-
end
|
|
128
|
+
super(mqttClient);
|
|
390
129
|
|
|
391
130
|
@jsonifyHashes = jsonify;
|
|
392
|
-
|
|
393
|
-
@conChangeMutex = Mutex.new();
|
|
394
|
-
@connected = false;
|
|
395
|
-
|
|
396
|
-
@mqtt.client_id ||= MQTT::Client.generate_client_id("MQTT_Sub_", 8);
|
|
397
|
-
|
|
398
|
-
@packetQueue = Array.new();
|
|
399
|
-
@packetQueueMutex = Mutex.new();
|
|
400
|
-
@publisherThreadWaiting = false;
|
|
401
|
-
|
|
402
|
-
@subscribedTopics = Hash.new();
|
|
403
|
-
|
|
404
|
-
@trackerHash = Hash.new();
|
|
405
|
-
|
|
406
|
-
@listenerThread = Thread.new do
|
|
407
|
-
ensure_clean_start();
|
|
408
|
-
mqtt_resub_thread();
|
|
409
|
-
end
|
|
410
|
-
@listenerThread.abort_on_exception = true;
|
|
411
|
-
|
|
412
|
-
begin
|
|
413
|
-
Timeout.timeout(5) {
|
|
414
|
-
until(@connected)
|
|
415
|
-
sleep 0.1;
|
|
416
|
-
end
|
|
417
|
-
}
|
|
418
|
-
rescue Timeout::Error
|
|
419
|
-
STDERR.puts "MQTT: #{@mqtt.host} did not connect!".red
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
@publisherThread = Thread.new do
|
|
423
|
-
mqtt_push_thread();
|
|
424
|
-
end
|
|
425
|
-
@publisherThread.abort_on_exception = true;
|
|
426
|
-
|
|
427
|
-
at_exit {
|
|
428
|
-
flush_pubqueue();
|
|
429
|
-
@connected = false;
|
|
430
|
-
@listenerThread.kill();
|
|
431
|
-
ensure_clean_exit();
|
|
432
|
-
}
|
|
433
131
|
end
|
|
434
132
|
end
|
|
435
133
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mqtt-sub_handler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.6.
|
|
4
|
+
version: 0.1.6.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Xasin
|
|
@@ -39,19 +39,19 @@ dependencies:
|
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: xasin-logger
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
47
|
+
version: '0.1'
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
54
|
+
version: '0.1'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: minitest
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -103,6 +103,7 @@ extra_rdoc_files: []
|
|
|
103
103
|
files:
|
|
104
104
|
- README.md
|
|
105
105
|
- lib/mqtt/Waitpoint.rb
|
|
106
|
+
- lib/mqtt/base_handler.rb
|
|
106
107
|
- lib/mqtt/mqtt_hash.rb
|
|
107
108
|
- lib/mqtt/persistence.rb
|
|
108
109
|
- lib/mqtt/persistence_extensions.rb
|