mqtt-sub_handler 0.0.1 → 0.0.2

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 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