mqtt-sub_handler 0.1.6.2 → 0.1.6.10
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 +5 -5
- data/lib/mqtt/base_handler.rb +353 -0
- data/lib/mqtt/sub_handler.rb +10 -299
- metadata +14 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 35fd43af3792d80dd5b218ad0b3e55c61c4d750d
|
4
|
+
data.tar.gz: afd508d2b76d30428d3c6c74ad44f12a7428b123
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec4ef80c35649a5ed3c56666d7797c9436a2f5aba2e02bfeea433c01be85e5343d7a5fb68fab03fde76953a1787527929ccf1f8f74138fb2913846901e21c3b6
|
7
|
+
data.tar.gz: '0200581a1aa3da0d1a33519d725e3b0d5c987fd54f6e9466e69f8638de2a3844abab71ba4384a8121539e03889961fe201977086a319ad2147c4516b0956e27c'
|
@@ -0,0 +1,353 @@
|
|
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_loge("Timeout on callback #{h}");
|
66
|
+
rescue => e
|
67
|
+
x_logf("Uncaught error on #{h}");
|
68
|
+
x_logf(e.inspect);
|
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
|
+
@packetQueueMutex.synchronize {
|
82
|
+
@packetQueue << data;
|
83
|
+
if(@packetQueue.size == 999)
|
84
|
+
x_logf("Packet queue congested, dropping packets!");
|
85
|
+
end
|
86
|
+
if(@packetQueue.size > 1000)
|
87
|
+
@packetQueue.shift
|
88
|
+
end
|
89
|
+
|
90
|
+
@publisherThread.run() if @publisherThreadWaiting;
|
91
|
+
}
|
92
|
+
end
|
93
|
+
private :queue_packet
|
94
|
+
|
95
|
+
# @!group Custom subscription handling
|
96
|
+
|
97
|
+
# Unregister a subscription. Removes it from the callback list and
|
98
|
+
# unsubscribes from the topic if no other subscriptions for it are present.
|
99
|
+
# @param subObject [MQTT::Subscriptions::Subscription]
|
100
|
+
# The subscription-object to remove
|
101
|
+
# @return void
|
102
|
+
def unregister_subscription(subObject)
|
103
|
+
raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscriptions::Subscription
|
104
|
+
return unless @callbackList.include? subObject;
|
105
|
+
|
106
|
+
queue_packet({type: :unsub, topic: subObject.topic});
|
107
|
+
@callbackList.delete(subObject);
|
108
|
+
end
|
109
|
+
# Register a custom subscription, and send a subscription message to the server.
|
110
|
+
# @param subObject [MQTT::Subscriptions::Subscription]
|
111
|
+
# An instance of a MQTT Subscription object
|
112
|
+
# @return void
|
113
|
+
def register_subscription(subObject)
|
114
|
+
raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscriptions::Subscription
|
115
|
+
return if @callbackList.include? subObject;
|
116
|
+
|
117
|
+
@callbackList << subObject;
|
118
|
+
queue_packet({type: :sub, topic: subObject.topic, qos: subObject.qos});
|
119
|
+
end
|
120
|
+
|
121
|
+
# @!endgroup
|
122
|
+
|
123
|
+
def ensure_clean_start()
|
124
|
+
@mqttWasStartedClean = @mqtt.clean_session
|
125
|
+
if @mqttWasStartedClean
|
126
|
+
begin
|
127
|
+
@mqtt.connect();
|
128
|
+
@mqtt.disconnect();
|
129
|
+
rescue MQTT::Exception
|
130
|
+
sleep 1;
|
131
|
+
retry
|
132
|
+
rescue SocketError, SystemCallError
|
133
|
+
sleep 5
|
134
|
+
retry
|
135
|
+
end
|
136
|
+
@mqtt.clean_session=false;
|
137
|
+
end
|
138
|
+
end
|
139
|
+
private :ensure_clean_start
|
140
|
+
|
141
|
+
def ensure_clean_exit()
|
142
|
+
if(@mqttWasStartedClean)
|
143
|
+
x_logi("Logging out.")
|
144
|
+
begin
|
145
|
+
Timeout.timeout(3) {
|
146
|
+
begin
|
147
|
+
@mqtt.clean_session = true;
|
148
|
+
@mqtt.disconnect();
|
149
|
+
@mqtt.connect();
|
150
|
+
rescue MQTT::Exception, SocketError, SystemCallError
|
151
|
+
sleep 0.3
|
152
|
+
retry;
|
153
|
+
end
|
154
|
+
}
|
155
|
+
rescue Timeout::Error
|
156
|
+
x_loge("Timed out, aborting!");
|
157
|
+
else
|
158
|
+
x_logi("Done");
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
private :ensure_clean_exit
|
163
|
+
|
164
|
+
def attempt_packet_publish()
|
165
|
+
until @packetQueue.empty? do
|
166
|
+
h = nil;
|
167
|
+
@packetQueueMutex.synchronize {
|
168
|
+
h = @packetQueue[0];
|
169
|
+
}
|
170
|
+
Timeout.timeout(3) {
|
171
|
+
if(h[:type] == :sub)
|
172
|
+
@mqtt.subscribe(h[:topic] => h[:qos]);
|
173
|
+
elsif(h[:type] == :pub)
|
174
|
+
@mqtt.publish(h[:topic], h[:data], h[:retain], h[:qos]);
|
175
|
+
end
|
176
|
+
}
|
177
|
+
@packetQueueMutex.synchronize {
|
178
|
+
@packetQueue.shift();
|
179
|
+
}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def mqtt_push_thread
|
184
|
+
@push_error_count = 0;
|
185
|
+
|
186
|
+
loop do
|
187
|
+
@packetQueueMutex.synchronize {
|
188
|
+
@publisherThreadWaiting = true;
|
189
|
+
}
|
190
|
+
x_logd("Push thread stopping")
|
191
|
+
sleep 1
|
192
|
+
x_logd("Push thread active")
|
193
|
+
@packetQueueMutex.synchronize {
|
194
|
+
@publisherThreadWaiting = false;
|
195
|
+
}
|
196
|
+
break if @destroying
|
197
|
+
|
198
|
+
next unless @connected
|
199
|
+
|
200
|
+
begin
|
201
|
+
attempt_packet_publish();
|
202
|
+
rescue MQTT::Exception, SocketError, SystemCallError, Timeout::Error => e
|
203
|
+
x_loge("Push error!");
|
204
|
+
x_loge(e.inspect);
|
205
|
+
|
206
|
+
@push_error_count += 1;
|
207
|
+
if(@push_error_count >= 10)
|
208
|
+
@mqtt.disconnect();
|
209
|
+
end
|
210
|
+
|
211
|
+
sleep 0.5
|
212
|
+
else
|
213
|
+
@push_error_count = 0;
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
x_logd("Push thread exited!");
|
218
|
+
end
|
219
|
+
private :mqtt_push_thread
|
220
|
+
|
221
|
+
def mqtt_resub_thread
|
222
|
+
loop do
|
223
|
+
begin
|
224
|
+
return if @destroying
|
225
|
+
|
226
|
+
x_logw("Trying to reconnect...");
|
227
|
+
Timeout.timeout(4) {
|
228
|
+
@mqtt.connect()
|
229
|
+
}
|
230
|
+
x_logi("Connected!");
|
231
|
+
@conChangeMutex.synchronize {
|
232
|
+
@connected = true;
|
233
|
+
@reconnectCount = 0;
|
234
|
+
}
|
235
|
+
|
236
|
+
@packetQueueMutex.synchronize {
|
237
|
+
@publisherThread.run() if (@publisherThread && @publisherThreadWaiting)
|
238
|
+
}
|
239
|
+
|
240
|
+
x_logd("Sub thread reading...");
|
241
|
+
@mqtt.get do |topic, message|
|
242
|
+
call_interested(topic, message);
|
243
|
+
end
|
244
|
+
rescue MQTT::Exception, Timeout::Error, SocketError, SystemCallError
|
245
|
+
x_loge("Disconnected!") if @connected
|
246
|
+
@connected = false;
|
247
|
+
@reconnectCount += 1;
|
248
|
+
|
249
|
+
@conChangeMutex.unlock if @conChangeMutex.owned?
|
250
|
+
@mqtt.clean_session = false;
|
251
|
+
sleep [0.1, 0.5, 1, 1, 5, 5, 5, 10, 10, 10, 10][@reconnectCount] || 30;
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
x_logd("Sub thread exited");
|
256
|
+
end
|
257
|
+
private :mqtt_resub_thread
|
258
|
+
|
259
|
+
def destroy!()
|
260
|
+
return if @destroying
|
261
|
+
@destroying = true;
|
262
|
+
|
263
|
+
unless @packetQueue.empty?
|
264
|
+
x_logd "Finishing sending of MQTT messages ... "
|
265
|
+
@publisherThread.run() if @publisherThreadWaiting
|
266
|
+
begin
|
267
|
+
Timeout.timeout(4) {
|
268
|
+
until @packetQueue.empty? do
|
269
|
+
sleep 0.05;
|
270
|
+
end
|
271
|
+
}
|
272
|
+
rescue Timeout::Error
|
273
|
+
x_logw "Not all messages were published";
|
274
|
+
else
|
275
|
+
x_logd "Publish clean finished"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
@publisherThread.run();
|
280
|
+
@publisherThread.join();
|
281
|
+
@listenerThread.kill();
|
282
|
+
|
283
|
+
@mqtt.disconnect() if @connected
|
284
|
+
|
285
|
+
ensure_clean_exit();
|
286
|
+
|
287
|
+
x_logi("Fully disconnected!");
|
288
|
+
end
|
289
|
+
|
290
|
+
# Initialize a new MQTT::SubHandler
|
291
|
+
# The handler immediately connects to the server, and begins receciving and sending.
|
292
|
+
# @param mqttClient [String, MQTT::Client] Either a URI to connect to, or a MQTT::Client
|
293
|
+
# The URI can be of the form "mqtts://Password@User:URL:port".
|
294
|
+
# The MQTT client instance can be fully configured, as specified by the MQTT Gem. It must *not* already be connected!
|
295
|
+
# @param jsonify [Boolean] Should Hashes and Arrays input into publish_to be converted to JSON?
|
296
|
+
# This can be useful to have one less .to_json call. Default is true.
|
297
|
+
# @example Starting the handler
|
298
|
+
# mqtt = MQTT::SubHandler.new('mqtt.eclipse.org');
|
299
|
+
# mqtt = MQTT::SubHandler.new(MQTT::Client.new("Your.Client.Opts"))
|
300
|
+
def initialize(mqttClient, logger: nil, **extra_opts)
|
301
|
+
@callbackList = Array.new();
|
302
|
+
if mqttClient.is_a? String
|
303
|
+
@mqtt = MQTT::Client.new(mqttClient);
|
304
|
+
@mqtt.clean_session = false unless extra_opts[:client_id].nil?
|
305
|
+
else
|
306
|
+
@mqtt = mqttClient;
|
307
|
+
end
|
308
|
+
|
309
|
+
init_x_log("MQTT #{@mqtt.host}", logger);
|
310
|
+
self.log_level = Logger::INFO;
|
311
|
+
|
312
|
+
@conChangeMutex = Mutex.new();
|
313
|
+
@connected = false;
|
314
|
+
@reconnectCount = 0;
|
315
|
+
|
316
|
+
@mqtt.client_id ||= extra_opts[:client_id] || MQTT::Client.generate_client_id("MQTT_Sub_", 8);
|
317
|
+
|
318
|
+
@packetQueue = Array.new();
|
319
|
+
@packetQueueMutex = Mutex.new();
|
320
|
+
|
321
|
+
@publisherThreadWaiting = false;
|
322
|
+
|
323
|
+
@subscribedTopics = Hash.new();
|
324
|
+
|
325
|
+
@trackerHash = Hash.new();
|
326
|
+
|
327
|
+
@listenerThread = Thread.new do
|
328
|
+
ensure_clean_start();
|
329
|
+
mqtt_resub_thread();
|
330
|
+
end
|
331
|
+
@listenerThread.abort_on_exception = true;
|
332
|
+
|
333
|
+
begin
|
334
|
+
Timeout.timeout(5) {
|
335
|
+
until(@connected)
|
336
|
+
sleep 0.1;
|
337
|
+
end
|
338
|
+
}
|
339
|
+
rescue Timeout::Error
|
340
|
+
x_loge("Broker did not connect!");
|
341
|
+
end
|
342
|
+
|
343
|
+
@publisherThread = Thread.new do
|
344
|
+
mqtt_push_thread();
|
345
|
+
end
|
346
|
+
@publisherThread.abort_on_exception = true;
|
347
|
+
|
348
|
+
at_exit {
|
349
|
+
destroy!()
|
350
|
+
}
|
351
|
+
end
|
352
|
+
end
|
353
|
+
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,117 +124,6 @@ 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
127
|
# Pause the main thread and wait for messages.
|
345
128
|
# This is mainly useful when the code has set everything up, but doesn't just want to end.
|
346
129
|
# "INT" is trapped, ensuring a smooth exit on Ctrl-C
|
@@ -349,87 +132,15 @@ class SubHandler
|
|
349
132
|
exit 0
|
350
133
|
}
|
351
134
|
|
352
|
-
|
135
|
+
x_logi("Main thread paused.")
|
353
136
|
Thread.stop();
|
354
137
|
end
|
355
|
-
|
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
|
138
|
+
alias lock_and_listen lockAndListen
|
372
139
|
|
373
|
-
|
374
|
-
|
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
|
-
def initialize(mqttClient, jsonify: true)
|
384
|
-
@callbackList = Array.new();
|
385
|
-
if mqttClient.is_a? String
|
386
|
-
@mqtt = MQTT::Client.new(mqttClient);
|
387
|
-
else
|
388
|
-
@mqtt = mqttClient;
|
389
|
-
end
|
140
|
+
def initialize(mqttClient, jsonify: true, **extra_opts)
|
141
|
+
super(mqttClient, **extra_opts);
|
390
142
|
|
391
143
|
@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
144
|
end
|
434
145
|
end
|
435
146
|
end
|
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.1.6.
|
4
|
+
version: 0.1.6.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xasin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mqtt
|
@@ -28,30 +28,30 @@ dependencies:
|
|
28
28
|
name: json
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.2'
|
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
|
@@ -128,7 +129,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
129
|
- !ruby/object:Gem::Version
|
129
130
|
version: '0'
|
130
131
|
requirements: []
|
131
|
-
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 2.5.2.1
|
132
134
|
signing_key:
|
133
135
|
specification_version: 4
|
134
136
|
summary: Asynchronous, topic-based MQTT gem
|