mqtt-sub_handler 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc152d507d338bd439708e9506b55c434f4ebeb4
4
- data.tar.gz: 7b9ce380c5ceda9d935d0defe51794723295376d
3
+ metadata.gz: aaf64955622e7422caca9dab6565f9ae9c80d11c
4
+ data.tar.gz: 804a85d87e0e22ac033342b96490d7e833143986
5
5
  SHA512:
6
- metadata.gz: a03ca9562da63543356deedfbba1117c877f5ebc80dac1da5883fbdb4e6a57e2eb17e28bb6bd554f999c9c97170a2dffe7a39a973ba79b0663fb9f09c2be4628
7
- data.tar.gz: aa8c44e52e0a2ff8893fa721f23be336770b7248bbcbd678d220b82d3a93e4cfb1d8f9795e79424f446f346eadb0a90b2b6339035b132e793da661ea5a3b5604
6
+ metadata.gz: 6876a2eb48079b48c30e3cdd8d9156c3fb57f11c63f63b1c1eadde490f96b2a9d41aafe9fd76dacd154866789692d8656701c41322decbf1f8b0930747481ff9
7
+ data.tar.gz: 3ab96f2b0af4d2f3bc0ce230f9d5f3d9bae4ba90447c4966c3d4b2d9fdf7af21164cb0ac3124805956d3c026f5948e82466fafc688f205c74c44a98554def34d
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+
2
+ **Important note:**
3
+ *Right now, I (Xasin) am still in the process of figuring out how to properly set up gems.
4
+ This is my first one, and it has only recently (11.02.18) been published.*
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
+ ---
9
+
10
+ # A topic-based, asynchronous MQTT handler
11
+ That's right, finally there's a *mostly* stable, connection-loss resistant MQTT handler out there that runs fully asynchronously.
12
+ What's even nicer is that you can attach (and detach!) callbacks to *any* topic you want - including wildcards!
13
+ Any topic can have as many callbacks as you want too, so don't worry about that!
14
+
15
+ ## That's great, but how to get it started?
16
+ The main code is simple:
17
+ ```ruby
18
+ require 'mqtt/sub_handler'
19
+
20
+ myClient = MQTT::Client.new(ADDRESS_OR_PARAMETERS); # See the "mqtt" gem for possible options!
21
+ mqttSubHandler = MQTT::SubHandler.new(myClient); # Create a handler with a mqtt class
22
+
23
+ mqttSubHandler = MQTT::SubHandler.new('mqtts://Password:Username@Address') # Or use a string!
24
+ mqttSubHandler = MQTT.Eclipse(); # Or use the quick shortcut to 'iot.eclipse.org' for testing!
25
+ ```
26
+ ### Subscribing
27
+
28
+ ```ruby
29
+ mySub = mqttSubHandler.subscribe_to "Any/Topic/You/Want" do |data, [topicMatch]|
30
+ puts "I got some data: #{data}";
31
+ end
32
+ # Yup, it's that simple. The callback will be stored, and will run whenever data is received.
33
+
34
+ # You need to get rid of your subscription?
35
+ # Sure thing!
36
+
37
+ mqttSubHandler.unregister_subscription(mySub); # That'll remove the callback, and unsubscribe!
38
+ # Don't worry about breaking other subscriptions. The code checks if any other callbacks are attached to the topic in question!
39
+ ```
40
+
41
+ #### Wildcard subscriptions
42
+ Wildcard subscriptions are also accepted with this code, and, may I say, are quite useful too!
43
+ ```ruby
44
+ mqttSubHandler.subscribe_to "A/Wildcard/+/Topic/+" do |data, topicList|
45
+ puts "I got some data: #{data}"
46
+ puts "The first + was: #{topicList[0]}"
47
+ puts "The second one was: #{topicList[1]}"
48
+ end
49
+ ```
50
+ Note: This also works with the "#" wildcard. Every following topic "branch" becomes a new array element!
51
+
52
+ ### Publishing
53
+ ```ruby
54
+ # Pushing gets easy, too:
55
+ mqttSubHandler.publish_to "Any/Topic/You/Want", theData, [qos: 1, retain: false]
56
+ # Right now, the code ONLY SUPPORTS QOS 0, AND WILL BLOCK!
57
+ # This is courtesy of the MQTT-Gem though, and nothing I can fix for now.
58
+ ```
59
+
60
+ ## But ... Wait?
61
+ Yep, you can wait for data, too!
62
+
63
+ ```ruby
64
+ mqttSubHandler.wait_for "The/Waiting/Topic", [timeout: seconds or nil] do |data, [topicMatch]|
65
+ # Confirm and process data here. The rest of the code will wait!
66
+ # Return true when you found what you need, or set an optional timeout, and the main code will continue after that.
67
+ end
68
+ ```
69
+
70
+ The same goes for the end of the code, if you have nothing else to run but want to keep listening:
71
+ ```ruby
72
+ mqttSubHandler.lockAndListen();
73
+ # This here also traps SIGINT, so you get a clean exit!
74
+ ```
@@ -4,17 +4,34 @@ require 'mqtt'
4
4
 
5
5
  require 'mqtt/subscription_classes'
6
6
 
7
+ # @author Xasin
7
8
  module MQTT
9
+ # A shortcut-function to quickly connect to the public Eclipse MQTT Broker.
10
+ # @return [MQTT::SubHandler] Sub-Handler connected to `iot.eclipse.org`
8
11
  def self.Eclipse()
9
12
  @EclipseMQTT ||= SubHandler.new('iot.eclipse.org');
10
13
  return @EclipseMQTT;
11
14
  end
12
15
 
16
+
13
17
  class SubHandler
18
+ # Split a Topic into a Topic-Array
19
+ # @param topicName [String] The string topic which to split
20
+ # @return [Array<String>] A list of individual topic-branches
21
+ # @note This function is mainly used for background processing.
14
22
  def self.getTopicSplit(topicName)
15
23
  return topicName.scan(/[^\/]+/);
16
24
  end
17
25
 
26
+ # Match a topic string to a topic pattern
27
+ # @param receivedTopicString [String] The string (as
28
+ # returned by MQTT.get) to compare
29
+ # @param topicPattern [Array<String>] The Topic-Array (as
30
+ # returned by .getTopicSplit) to compare against
31
+ # @return [nil, Array<String>] Nil if no match was found.
32
+ # An Array of matched wildcard topic branches (can be empty) when
33
+ # successfully matched
34
+ # @note (see .getTopicSplit)
18
35
  def self.getTopicMatch(receivedTopicString, topicPattern)
19
36
  receivedTopicList = getTopicSplit receivedTopicString;
20
37
 
@@ -40,6 +57,7 @@ class SubHandler
40
57
  return nil;
41
58
  end
42
59
 
60
+ # Call all existing callbacks whose topic-list matches `topic`
43
61
  def call_interested(topic, data)
44
62
  topicHasReceivers = false;
45
63
  @callbackList.each do |h|
@@ -54,6 +72,7 @@ class SubHandler
54
72
  end
55
73
  private :call_interested
56
74
 
75
+ # Handle sending a subscription-message to the server
57
76
  def raw_subscribe_to(topic, qos: 1)
58
77
  begin
59
78
  @conChangeMutex.lock
@@ -71,48 +90,82 @@ class SubHandler
71
90
  end
72
91
  private :raw_subscribe_to
73
92
 
93
+ # @!group Custom subscription handling
94
+
95
+ # Unregister a subscription. Removes it from the callback list and
96
+ # unsubscribes from the topic if no other subscriptions for it are present.
97
+ # @param subObject [MQTT::Subscriptions::Subscription]
98
+ # The subscription-object to remove
99
+ # @return void
74
100
  def unregister_subscription(subObject)
75
- raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscription
101
+ raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscriptions::Subscription
76
102
  return unless @callbackList.include? subObject;
77
103
 
78
104
  @callbackList.delete(subObject);
79
105
  end
106
+ # Register a custom subscription, and send a subscription message to the server.
107
+ # @param subObject [MQTT::Subscriptions::Subscription]
108
+ # An instance of a MQTT Subscription object
109
+ # @return void
80
110
  def register_subscription(subObject)
81
- raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscription
111
+ raise ArgumentError, "Object is not a subscription!" unless subObject.is_a? MQTT::Subscriptions::Subscription
82
112
  return if @callbackList.include? subObject;
83
113
 
84
114
  @callbackList << subObject;
85
115
  raw_subscribe_to(subObject.topic, qos: subObject.qos);
86
116
  end
87
117
 
118
+ # @!group Subscribing
119
+
120
+ # Synchronously wait for data.
121
+ # It waits for a message on `topic`, optionally letting a block
122
+ # check the data for validity, and optionally aborting after a timeout
123
+ # @param topic [String] The MQTT-Topic to wait for
124
+ # @param timeout [nil, Integer] The optional timeout after which to abort
125
+ # @param qos [nil, Integer] The QoS for this subscription
126
+ # @return [Boolean] True if the block returned true, False if the code timed-out
127
+ # @yieldparam data [String] The data received via MQTT
128
+ # @yieldparam topicList [Array<String>] The wildcard topic branches matched.
129
+ # @yieldreturn [Boolean] Whether or not the data was sufficient, and capture should be stopped.
88
130
  def wait_for(topic, qos: 1, timeout: nil)
89
- subObject = MQTT::WaitpointSubscription.new(topic, qos);
131
+ unless block_given? then
132
+ raise ArgumentError, "A block for data-processing needs to be passed!"
133
+ end
134
+
135
+ subObject = MQTT::Subscriptions::WaitpointSubscription.new(topic, qos);
90
136
  register_subscription(subObject);
91
137
 
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
138
+ begin
139
+ Timeout::timeout(timeout) do
140
+ loop do
141
+ return_data = subObject.waitpoint.wait()[1];
142
+ if yield(return_data[0], return_data[1]) then
143
+ return true;
101
144
  end
102
145
  end
103
- rescue Timeout::Error
104
- return false;
105
- end
106
- else
107
- return_data = subObject.waitpoint.wait(timeout);
108
146
  end
109
-
110
- unregister_subscription(subObject);
111
- return return_data;
147
+ rescue Timeout::Error
148
+ return false;
149
+ ensure
150
+ unregister_subscription(subObject);
151
+ end
112
152
  end
153
+
154
+ # Track data changes for a topic in the background.
155
+ # With no callback given, the returned object can be used to get the last
156
+ # received raw data string.
157
+ # With a callback given, the callback will be called whenever a change in data
158
+ # is detected.
159
+ # @param topic [String] The MQTT-Topic to track for data. Can be a Wildcard.
160
+ # @param qos [nil, Integer] The QoS to use for the subscription
161
+ # @yieldparam data [String] The new (changed) data received from MQTT.
162
+ # @yieldreturn [void]
163
+ # @return [MQTT::Subscriptions::ValueTrackerSubscription]
164
+ # The tracker-object. Can be used to unsubscribe.
165
+ # More importantly, `tracker.value` can be used to fetch the last received data.
113
166
  def track(topic, qos: 1, &callback)
114
167
  unless(@trackerHash.has_key? topic)
115
- subObject = MQTT::ValueTrackerSubscription.new(topic, qos);
168
+ subObject = MQTT::Subscriptions::ValueTrackerSubscription.new(topic, qos);
116
169
  register_subscription(subObject);
117
170
 
118
171
  @trackerHash[topic] = subObject;
@@ -123,15 +176,33 @@ class SubHandler
123
176
  return @trackerHash[topic];
124
177
  end
125
178
  alias on_change track
179
+
180
+ # Attach a callback to a MQTT Topic or wildcard.
181
+ # The callback will be saved, and asynchronously executed whenever a message
182
+ # from a matching topic (including wildcards) is received.
183
+ # @param topic [String] The MQTT-Topic to subscribe to. Can be a Wildcard.
184
+ # @param qos [nil, Integer] The QoS for the subscription. Currently not used!
185
+ # @yieldparam data [String] The raw MQTT data received from the MQTT server
186
+ # @yieldparam topicList [Array<String>] An array of topic-branches corresponding to wildcard matches.
187
+ # Can be empty if no wildcard was used!
188
+ # @yieldreturn [void]
189
+ # @return [MQTT::Subscriptions::CallbackSubscription] The Subscription-Object corresponding to this callback.
190
+ # Mainly used by the .unregister_subscription function to unsubscribe.
126
191
  def subscribe_to(topic, qos: 1, &callback)
127
- subObject = MQTT::CallbackSubscription.new(topic, qos, callback);
192
+ subObject = MQTT::Subscriptions::CallbackSubscription.new(topic, qos, callback);
128
193
  register_subscription(subObject);
129
194
 
130
195
  return subObject;
131
196
  end
132
197
  alias subscribeTo subscribe_to
133
198
 
199
+ # @!endgroup
134
200
 
201
+ # Publish a message to topic.
202
+ # @param topic [String] The topic to push to.
203
+ # @param data [String] The data to be transmitted.
204
+ # @param qos [nil, Numeric] QoS for the publish. Currently not fully supported by the mqtt gem.
205
+ # @param retain [nil, Boolean] retain-flag for the publish.
135
206
  def publish_to(topic, data, qos: 1, retain: false)
136
207
  raise ArgumentError, "Wrong symbol in topic: #{topic}" if topic =~ /[#\+]/
137
208
 
@@ -186,6 +257,9 @@ class SubHandler
186
257
  end
187
258
  private :mqtt_resub_thread
188
259
 
260
+ # Pause the main thread and wait for messages.
261
+ # This is mainly useful when the code has set everything up, but doesn't just want to end.
262
+ # "INT" is trapped, ensuring a smooth exit on Ctrl-C
189
263
  def lockAndListen()
190
264
  Signal.trap("INT") {
191
265
  exit 0
@@ -213,8 +287,14 @@ class SubHandler
213
287
  end
214
288
  end
215
289
  end
216
-
217
- def initialize(mqttClient, autoListen: true)
290
+ private :flush_pubqueue
291
+
292
+ # Initialize a new MQTT::SubHandler
293
+ # The handler immediately connects to the server, and begins receciving and sending.
294
+ # @param mqttClient [String, MQTT::Client] Either a URI to connect to, or a MQTT::Client
295
+ # The URI can be of the form "mqtts://Password@User:URL:port"
296
+ # The MQTT client instance can be fully configured, as specified by the MQTT Gem. It must **not** already be connected!
297
+ def initialize(mqttClient)
218
298
  @callbackList = Array.new();
219
299
  if mqttClient.is_a? String then
220
300
  @mqtt = MQTT::Client.new(mqttClient);
@@ -2,74 +2,77 @@
2
2
  require 'mqtt/Waitpoint.rb'
3
3
 
4
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);
5
+ module Subscriptions
6
+ # @abstract Basis for custom MQTT::Subcription objects
7
+ class Subscription
8
+ attr_reader :topic
9
+ attr_reader :qos
10
+ attr_reader :topic_split
11
+
12
+ def initialize(topic, qos)
13
+ @topic = topic;
14
+ @topic_split = SubHandler.getTopicSplit(topic);
15
+
16
+ @qos = 0;
17
+ end
13
18
 
14
- @qos = 0;
19
+ def offer(topicList, data) end
15
20
  end
16
21
 
17
- def offer(topicList, data) end
18
- end
22
+ class CallbackSubscription < Subscription
23
+ def initialize(topic, qos, callback)
24
+ super(topic, qos);
19
25
 
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);
26
+ @callback = callback;
27
+ end
28
+ def offer(topicList, data)
29
+ @callback.call(data, topicList);
30
+ end
28
31
  end
29
- end
30
32
 
31
- class WaitpointSubscription < Subscription
32
- attr_reader :waitpoint
33
+ class WaitpointSubscription < Subscription
34
+ attr_reader :waitpoint
33
35
 
34
- def initialize(topic, qos)
35
- super(topic, qos);
36
+ def initialize(topic, qos)
37
+ super(topic, qos);
36
38
 
37
- @waitpoint = Xasin::Waitpoint.new();
38
- end
39
+ @waitpoint = Xasin::Waitpoint.new();
40
+ end
39
41
 
40
- def offer(topicList, data)
41
- @waitpoint.fire([topicList, data]);
42
+ def offer(topicList, data)
43
+ @waitpoint.fire([data, topicList]);
44
+ end
42
45
  end
43
- end
44
46
 
45
- class ValueTrackerSubscription < Subscription
46
- attr_reader :value
47
+ class ValueTrackerSubscription < Subscription
48
+ attr_reader :value
47
49
 
48
- def initialize(topic, qos = 1)
49
- raise ArgumentError, "Tracking of topic wildcards is prohibited! Topic: #{topic}" if topic =~ /[#\+]/
50
- super(topic, qos);
50
+ def initialize(topic, qos = 1)
51
+ raise ArgumentError, "Tracking of topic wildcards is prohibited! Topic: #{topic}" if topic =~ /[#\+]/
52
+ super(topic, qos);
51
53
 
52
- @value = nil;
54
+ @value = nil;
53
55
 
54
- @callbackList = Array.new();
55
- end
56
+ @callbackList = Array.new();
57
+ end
56
58
 
57
- def offer(topicList, data)
58
- return if data == @value;
59
+ def offer(topicList, data)
60
+ return if data == @value;
59
61
 
60
- @callbackList.each do |cb|
61
- cb.call(data, @value);
62
+ @callbackList.each do |cb|
63
+ cb.call(data, @value);
64
+ end
65
+ @value = data;
62
66
  end
63
- @value = data;
64
- end
65
67
 
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;
68
+ def attach(callback)
69
+ @callbackList << callback;
70
+ callback.call(@value, nil) if(@value);
71
+ return callback;
72
+ end
73
+ def detach(callback)
74
+ @callbackList.delete callback;
75
+ end
73
76
  end
74
77
  end
75
78
  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.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xasin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-10 00:00:00.000000000 Z
11
+ date: 2018-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mqtt
@@ -24,12 +24,14 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.5.0
27
- description: A gem to use when the normal ruby mqtt doesn't hit the spot.
27
+ description: Asynchronous handling of callbacks that can be attached to individual
28
+ topics, based on the mqtt gem.
28
29
  email:
29
30
  executables: []
30
31
  extensions: []
31
32
  extra_rdoc_files: []
32
33
  files:
34
+ - README.md
33
35
  - lib/mqtt/Waitpoint.rb
34
36
  - lib/mqtt/sub_handler.rb
35
37
  - lib/mqtt/subscription_classes.rb