apiotics_aws_iot_client 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +13 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +201 -0
  5. data/README.md +207 -0
  6. data/Rakefile +6 -0
  7. data/apiotics-aws-iot-client-1.0.0.gem +0 -0
  8. data/apiotics_aws_client-1.0.1.gem +0 -0
  9. data/apiotics_aws_iot_client.gemspec +40 -0
  10. data/bin/console +11 -0
  11. data/bin/setup +7 -0
  12. data/codeclimate.yml +6 -0
  13. data/lib/aws_iot_device.rb +8 -0
  14. data/lib/aws_iot_device/mqtt_adapter.rb +31 -0
  15. data/lib/aws_iot_device/mqtt_adapter/client.rb +207 -0
  16. data/lib/aws_iot_device/mqtt_adapter/paho_mqtt_adapter.rb +207 -0
  17. data/lib/aws_iot_device/mqtt_adapter/ruby_mqtt_adapter.rb +183 -0
  18. data/lib/aws_iot_device/mqtt_shadow_client.rb +7 -0
  19. data/lib/aws_iot_device/mqtt_shadow_client/json_payload_parser.rb +34 -0
  20. data/lib/aws_iot_device/mqtt_shadow_client/mqtt_manager.rb +201 -0
  21. data/lib/aws_iot_device/mqtt_shadow_client/shadow_action_manager.rb +318 -0
  22. data/lib/aws_iot_device/mqtt_shadow_client/shadow_client.rb +118 -0
  23. data/lib/aws_iot_device/mqtt_shadow_client/shadow_topic_manager.rb +75 -0
  24. data/lib/aws_iot_device/mqtt_shadow_client/token_creator.rb +36 -0
  25. data/lib/aws_iot_device/mqtt_shadow_client/topic_builder.rb +35 -0
  26. data/lib/aws_iot_device/version.rb +3 -0
  27. data/samples/config_shadow.rb +72 -0
  28. data/samples/mqtt_client_samples/mqtt_client_samples.rb +38 -0
  29. data/samples/shadow_action_samples/sample_shadow_action_update.rb +25 -0
  30. data/samples/shadow_client_samples/samples_shadow_client_block.rb +25 -0
  31. data/samples/shadow_client_samples/samples_shadow_client_delete.rb +24 -0
  32. data/samples/shadow_client_samples/samples_shadow_client_description.rb +64 -0
  33. data/samples/shadow_client_samples/samples_shadow_client_get.rb +23 -0
  34. data/samples/shadow_client_samples/samples_shadow_client_getting_started.rb +22 -0
  35. data/samples/shadow_client_samples/samples_shadow_client_update.rb +30 -0
  36. data/samples/shadow_topic_samples/sample_topic_manager.rb +26 -0
  37. metadata +217 -0
@@ -0,0 +1,183 @@
1
+ require 'mqtt'
2
+ require 'thread'
3
+
4
+ module AwsIotDevice
5
+ module MqttAdapter
6
+ class RubyMqttAdapter
7
+
8
+ attr_reader :client_id
9
+
10
+ attr_accessor :filtered_topics
11
+
12
+ def initialize(*args)
13
+ @client = MQTT::Client.new(*args)
14
+ @filtered_topics = {}
15
+ @client_id = ""
16
+ @client_id = generate_client_id
17
+ end
18
+
19
+ def client_id
20
+ @client_id
21
+ end
22
+
23
+ def publish(topic, payload='', retain=false, qos=0)
24
+ @client.publish(topic, payload, retain, qos)
25
+ end
26
+
27
+ def create_client(*args)
28
+ @client = MQTT::Client.new(*args)
29
+ end
30
+
31
+ def connect(*args, &block)
32
+ @client = create_client(*args) if @client.nil?
33
+ @client.connect(&block)
34
+ loop_start
35
+ end
36
+
37
+ def generate_client_id(prefix='ruby', lenght=16)
38
+ charset = Array('A'..'Z') + Array('a'..'z') + Array('0'..'9')
39
+ @client_id << prefix << Array.new(lenght) { charset.sample }.join
40
+ end
41
+
42
+ def ssl_context
43
+ @client.ssl_context
44
+ end
45
+
46
+ def disconnect(send_msg=true)
47
+ @client.disconnect(send_msg)
48
+ end
49
+
50
+ def connected?
51
+ @client.connected?
52
+ end
53
+
54
+ def subscribe(topics, qos=0)
55
+ @client.subscribe(topics, qos)
56
+ end
57
+
58
+ def get(topic=nil, &block)
59
+ @client.get(topic, &block)
60
+ end
61
+
62
+ def get_packet(topic=nil, &block)
63
+ @client.get_packet(topic, &block)
64
+ end
65
+
66
+ def queue_empty?
67
+ @client.queue_empty?
68
+ end
69
+
70
+ def queue_length
71
+ @client.queue_length
72
+ end
73
+
74
+ def unsubscribe(*topics)
75
+ @client.unsubscribe(*topics)
76
+ end
77
+
78
+ def host
79
+ @client.host
80
+ end
81
+
82
+ def host=(host)
83
+ @client.host = host
84
+ end
85
+
86
+ def port
87
+ @client.port
88
+ end
89
+
90
+ def port=(port)
91
+ @client.port = port
92
+ end
93
+
94
+ def ssl=(ssl)
95
+ @client.ssl = ssl
96
+ end
97
+
98
+ def set_tls_ssl_context(ca_cert, cert=nil, key=nil)
99
+ @client.ssl ||= true
100
+ @client.ssl_context
101
+ @client.cert_file = cert
102
+ @client.key_file = key
103
+ @client.ca_file = ca_cert
104
+ end
105
+
106
+ ############### Custom Features #################
107
+
108
+ def loop_start
109
+ Thread.new{ loop_forever }
110
+ end
111
+
112
+ def loop_stop(thread)
113
+ thread.join
114
+ end
115
+
116
+ def loop_forever
117
+ loop do
118
+ mqtt_loop
119
+ sleep 0.005
120
+ end
121
+ end
122
+
123
+ def mqtt_loop
124
+ loop_read
125
+ loop_write
126
+ loop_misc
127
+ end
128
+
129
+ def loop_read(max_message=10)
130
+ counter_message = 0
131
+ while !@client.queue_empty? && counter_message <= max_message
132
+ message = get_packet
133
+ ### Fitlering message if matching to filtered topic
134
+ topic = message.topic
135
+ if @filtered_topics.key?(topic)
136
+ callback = @filtered_topics[topic]
137
+ callback.call(message)
138
+ else
139
+ on_message_callback(message)
140
+ end
141
+ counter_message += 1
142
+ end
143
+ end
144
+
145
+ def loop_write
146
+ puts "loop_write is unavailable for ruby-mqtt client's"
147
+ end
148
+
149
+ def loop_misc
150
+ puts "loop_misc is unavailable for ruby-mqtt client's"
151
+ end
152
+
153
+ def on_message=(callback)
154
+ @on_message = callback
155
+ end
156
+
157
+ def on_message(&block)
158
+ @on_message = block if block_given?
159
+ @on_message
160
+ end
161
+
162
+ def on_message_callback(message)
163
+ if @on_message.is_a?(Proc) || @on_message.lambda?
164
+ @on_message.call(message)
165
+ end
166
+ end
167
+
168
+ def add_callback_filter_topic(topic, callback)
169
+ unless callback.nil?
170
+ @filtered_topics[topic] = callback
171
+ else
172
+ @filtered_topic.delete(topic)
173
+ end
174
+ end
175
+
176
+ def remove_callback_filter_topic(topic)
177
+ if @filtered_topics.key(topic)
178
+ @filtered_topics.delete(topic)
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,7 @@
1
+ require 'aws_iot_device/mqtt_shadow_client/shadow_client'
2
+
3
+ module AwsIotDevice
4
+ module MqttShadowClient
5
+ ACTION_NAME = %w(get update delete delta).freeze
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ require 'securerandom'
2
+ require 'timers'
3
+ require 'thread'
4
+ require 'json'
5
+
6
+ module AwsIotDevice
7
+ module MqttShadowClient
8
+ class JSONPayloadParser
9
+ ### This class acts as Basic JSON parser.
10
+ ### The answer from AWS is in a JSON format.
11
+ ### All different key of the JSON file should be defined as hash key
12
+
13
+ def initialize
14
+ @message = {}
15
+ end
16
+
17
+ def set_message(message)
18
+ @message = JSON.parse(message)
19
+ end
20
+
21
+ def get_attribute_value(key)
22
+ @message[key]
23
+ end
24
+
25
+ def set_attribute_value(key, value)
26
+ @message[key] = value
27
+ end
28
+
29
+ def get_json
30
+ @message.to_json
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,201 @@
1
+ require 'thread'
2
+
3
+ module AwsIotDevice
4
+ module MqttShadowClient
5
+ class MqttManager
6
+
7
+ attr_reader :client_id
8
+
9
+ attr_accessor :connection_timeout_s
10
+
11
+ attr_accessor :mqtt_operation_timeout_s
12
+
13
+ attr_accessor :ssl
14
+
15
+ def initialize(*args)
16
+ @client = create_mqtt_adapter(*args)
17
+ @mqtt_operation_timeout_s = 2
18
+ @mutex_publish = Mutex.new()
19
+ @mutex_subscribe = Mutex.new()
20
+ @mutex_unsubscribe = Mutex.new()
21
+ end
22
+
23
+ def host=(host)
24
+ @client.host = host
25
+ end
26
+
27
+ def host
28
+ @client.host
29
+ end
30
+
31
+ def port=(port)
32
+ @client.port = port
33
+ end
34
+
35
+ def port
36
+ @client.port
37
+ end
38
+
39
+ def client_id
40
+ @client.client_id
41
+ end
42
+
43
+ def create_mqtt_adapter(*args)
44
+ @client = MqttAdapter::Client.new(*args)
45
+ end
46
+
47
+ def config_endpoint(host, port)
48
+ raise ArgumentError, "configure endpoint either host or port is nil" if host.nil? || port.nil?
49
+ @client.host = host
50
+ @client.port = port
51
+ end
52
+
53
+ def config_ssl_context(ca_file, key, cert)
54
+ self.ca_file = ca_file
55
+ self.key = key
56
+ self.cert = cert
57
+ @client.set_tls_ssl_context(@ca_file, @cert, @key)
58
+ end
59
+
60
+ def connect(*args, &block)
61
+ ### Execute a mqtt opration loop in background for time period defined by mqtt_connection_timeout
62
+ @client.connect(*args, &block)
63
+ end
64
+
65
+ def disconnect
66
+ @client.disconnect
67
+ end
68
+
69
+ def publish(topic, payload="", retain=nil, qos=0)
70
+ raise ArgumentError, "publish topic cannot be nil" if topic.nil?
71
+ @mutex_publish.synchronize {
72
+ @client.publish(topic, payload, retain, qos)
73
+ }
74
+ end
75
+
76
+ def subscribe(topic, qos=0, callback=nil)
77
+ raise ArgumentError, "subscribe topic cannot be nil" if topic.nil?
78
+ @mutex_subscribe.synchronize {
79
+ @client.add_callback_filter_topic(topic, callback)
80
+ @client.subscribe(topic, qos)
81
+ }
82
+ end
83
+
84
+ def subscribe_bunch(*topics)
85
+ @mutex_subscribe.synchronize {
86
+ topics.each do |topic|
87
+ @client.add_callback_filter_topic(topic.first, topic.pop) if !topic[2].nil? && topic[2].is_a?(Proc)
88
+ end
89
+ @client.subscribe_bunch(topics)
90
+ }
91
+ end
92
+
93
+ def unsubscribe(topic)
94
+ raise ArgumentError, "unsubscribe topic cannot be nil" if topic.nil?
95
+ @mutex_unsubscribe.synchronize{
96
+ @client.remove_callback_filter_topic(topic)
97
+ @client.unsubscribe(topic)
98
+ }
99
+ end
100
+
101
+ def unsubscribe_bunch(*topics)
102
+ @mutex_unsubscribe.synchronize {
103
+ topics.each do |topic|
104
+ @client.remove_callback_filter_topic(topic)
105
+ end
106
+ @client.unsubscribe_bunch(topics)
107
+ }
108
+ end
109
+
110
+ def on_connack=(callback)
111
+ @client.on_connack = callback if paho_client?
112
+ end
113
+
114
+ def on_suback=(callback)
115
+ @client.on_suback = callback if paho_client?
116
+ end
117
+
118
+ def on_unsuback=(callback)
119
+ @client.on_unsuback = callback if paho_client?
120
+ end
121
+
122
+ def on_puback=(callback)
123
+ @client.on_puback = callback if paho_client?
124
+ end
125
+
126
+ def on_pubrec=(callback)
127
+ @client.on_pubrec = callback if paho_client?
128
+ end
129
+
130
+ def on_pubrel=(callback)
131
+ @client.on_pubrel = callback if paho_client?
132
+ end
133
+
134
+ def on_pubcomp=(callback)
135
+ @client.on_pubcomp = callback if paho_client?
136
+ end
137
+
138
+ def on_message=(callback)
139
+ @client.on_message = callback
140
+ end
141
+
142
+ def on_connack(&block)
143
+ @client.on_connack(&block) if paho_client?
144
+ end
145
+
146
+ def on_suback(&block)
147
+ @client.on_suback(&block) if paho_client?
148
+ end
149
+
150
+ def on_unsuback(&block)
151
+ @client.on_unsuback(&block) if paho_client?
152
+ end
153
+
154
+ def on_puback(&block)
155
+ @client.on_puback(&block) if paho_client?
156
+ end
157
+
158
+ def on_pubrec(&block)
159
+ @client.on_pubrec(&block) if paho_client?
160
+ end
161
+
162
+ def on_pubrel(&block)
163
+ @client.on_pubrel(&block) if paho_client?
164
+ end
165
+
166
+ def on_pubcomp(&block)
167
+ @client.on_pubcomp(&block) if paho_client?
168
+ end
169
+
170
+ def on_message(&block)
171
+ @client.on_message(&block)
172
+ end
173
+
174
+ def add_topic_callback(topic, callback, &block)
175
+ @client.add_callback_filter_topic(topic, callback, &block)
176
+ end
177
+
178
+ def remove_topic_callback(topic)
179
+ @client.remove_callback_filter_topic(topic)
180
+ end
181
+
182
+ def paho_client?
183
+ @client.adapter.class == MqttAdapter::PahoMqttAdapter
184
+ end
185
+
186
+ private
187
+
188
+ def cert=(path)
189
+ @cert = path
190
+ end
191
+
192
+ def key=(path)
193
+ @key = path
194
+ end
195
+
196
+ def ca_file=(path)
197
+ @ca_file = path
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,318 @@
1
+ require 'securerandom'
2
+ require 'timers'
3
+ require 'thread'
4
+ require 'json'
5
+ require 'aws_iot_device/mqtt_shadow_client/token_creator'
6
+ require 'aws_iot_device/mqtt_shadow_client/json_payload_parser'
7
+
8
+ module AwsIotDevice
9
+ module MqttShadowClient
10
+ class ShadowActionManager
11
+ ### This the main AWS action manager
12
+ ### It enables the AWS IoT actions (get, update, delete)
13
+ ### It enables the time control the time out after an action have been start
14
+ ### Actions requests are send on the general actions topic and answer is retreived from accepted/refused/delta topics
15
+
16
+ attr_accessor :logger
17
+
18
+ def initialize(shadow_name, mqtt_client, persistent_subscribe=false)
19
+ @shadow_name = shadow_name
20
+ @topic_manager = ShadowTopicManager.new(mqtt_client, shadow_name)
21
+ @payload_parser = JSONPayloadParser.new
22
+ @is_subscribed = {}
23
+ @is_subscribed[:get] = false
24
+ @is_subscribed[:update] = false
25
+ @is_subscribed[:delete] = false
26
+ @token_handler = TokenCreator.new(shadow_name, mqtt_client.client_id)
27
+ @persistent_subscribe = persistent_subscribe
28
+ @last_stable_version = -1 #Mean no currentely stable
29
+ @topic_subscribed_callback = {}
30
+ @topic_subscribed_callback[:get] = nil
31
+ @topic_subscribed_callback[:update] = nil
32
+ @topic_subscribed_callback[:delta] = nil
33
+ @topic_subscribed_task_count = {}
34
+ @topic_subscribed_task_count[:get] = 0
35
+ @topic_subscribed_task_count[:update] = 0
36
+ @topic_subscribed_task_count[:delete] = 0
37
+ @token_pool = {}
38
+ @token_callback = {}
39
+ @task_count_mutex = Mutex.new
40
+ @token_mutex = Mutex.new
41
+ @parser_mutex = Mutex.new
42
+ set_basic_callback
43
+ end
44
+
45
+ ### Send and publish packet with an empty payload contains in a valid JSON format.
46
+ ### A unique token is generate and send in the packet in order to trace the action.
47
+ ### Subscribe to the two get/accepted and get/rejected of the coresponding shadow.
48
+ ### If the request is accpeted, the answer would be send on the get/accepted topic.
49
+ ### It contains all the details of the shadow state in JSON document.
50
+ ### A specific callback in Proc could be send parameter.
51
+ ### Before exit, the function start a timer count down in the separate thread.
52
+ ### If the time ran out, the timer_handler function is called and the get action is cancelled using the token.
53
+ ###
54
+ ### Parameter:
55
+ ### > callback: the Proc to execute when the answer to th get request would be received.
56
+ ### It should accept three different paramter:
57
+ ### - payload : the answer content
58
+ ### - response_status : among ['accepted', 'refused', 'delta']
59
+ ### - token : the token assoicate to the get request
60
+ ###
61
+ ### > timeout: the period after which the request should be canceled and timer_handler should be call
62
+ ###
63
+ ### Returns :
64
+ ### > the token associate to the current action (which also store in @token_pool)
65
+
66
+ def shadow_get(timeout=5, callback=nil, &block)
67
+ shadow_action(:get, "", timeout, callback, &block)
68
+ end
69
+
70
+ def shadow_update(payload, timeout=5, callback=nil, &block)
71
+ shadow_action(:update, payload, timeout, callback, &block)
72
+ end
73
+
74
+ def shadow_delete(timeout=5, callback=nil, &block)
75
+ shadow_action(:delete, "", timeout, callback, &block)
76
+ end
77
+
78
+ def register_get_callback(callback, &block)
79
+ register_action_callback(:get, callback, &block)
80
+ end
81
+
82
+ def register_update_callback(callback, &block)
83
+ register_action_callback(:update, callback, &block)
84
+ end
85
+
86
+ def register_delete_callback(callback, &block)
87
+ register_action_callback(:delete, callback, &block)
88
+ end
89
+
90
+ def register_shadow_delta_callback(callback, &block)
91
+ if callback.is_a?(Proc)
92
+ @topic_subscribed_callback[:delta] = callback
93
+ elsif block_given?
94
+ @topic_subscribed_callback[:delta] = block
95
+ end
96
+ @topic_manager.shadow_topic_subscribe("delta", @default_callback)
97
+ end
98
+
99
+ def remove_get_callback
100
+ remove_action_callback(:get)
101
+ end
102
+
103
+ def remove_update_callback
104
+ remove_action_callback(:update)
105
+ end
106
+
107
+ def remove_delete_callback
108
+ remove_action_callback(:delete)
109
+ end
110
+
111
+ def remove_shadow_delta_callback
112
+ @topic_subscribe_callback.delete[:delta]
113
+ @topic_manager.shadow_topic_unsubscribe("delta")
114
+ end
115
+
116
+ def logger?
117
+ !@logger.nil? && @logger.is_a?(Logger)
118
+ end
119
+
120
+ private
121
+
122
+ def shadow_action(action, payload="", timeout=5, callback=nil, &block)
123
+ current_token = Symbol
124
+ timer = Timers::Group.new
125
+ json_payload = ""
126
+ @token_mutex.synchronize(){
127
+ current_token = @token_handler.create_next_token
128
+ }
129
+ timer.after(timeout){ timeout_manager(action, current_token) }
130
+ @parser_mutex.synchronize {
131
+ @payload_parser.set_message(payload) unless payload == ""
132
+ @payload_parser.set_attribute_value("clientToken", current_token)
133
+ json_payload = @payload_parser.get_json
134
+ }
135
+ handle_subscription(action, timeout) unless @is_subscribed[action]
136
+ @topic_manager.shadow_topic_publish(action.to_s, json_payload)
137
+ @task_count_mutex.synchronize {
138
+ @topic_subscribed_task_count[action] += 1
139
+ }
140
+ @token_pool[current_token] = timer
141
+ register_token_callback(current_token, callback, &block)
142
+ Thread.new{ timer.wait }
143
+ current_token
144
+ end
145
+
146
+ ### Should cancel the token after a preset time interval
147
+ def timeout_manager(action_name, token)
148
+ if @token_pool.has_key?(token)
149
+ action = action_name.to_sym
150
+ @token_pool.delete(token)
151
+ @token_callback.delete(token)
152
+ @logger.warn("The #{action_name} request with the token #{token} has timed out!\n") if logger?
153
+ @task_count_mutex.synchronize {
154
+ @topic_subscribed_task_count[action] -= 1
155
+ unless @topic_subscribed_task_count[action] <= 0
156
+ @topic_subscribed_task_count[action] = 0
157
+ unless @persistent_subscribe
158
+ @topic_manager.shadow_topic_unsubscribe(action)
159
+ @is_subscribed[action.to_sym] = false
160
+ end
161
+ end
162
+ }
163
+ end
164
+ end
165
+
166
+ def set_basic_callback
167
+ @default_callback = proc { |message| do_message_callback(message) }
168
+
169
+ @topic_manager.on_suback = lambda do |topics|
170
+ action = @topic_manager.retrieve_action(topics[0])
171
+ @is_subscribed[action] ||= true unless action.nil?
172
+ end
173
+
174
+ @topic_manager.on_unsuback = lambda do |topics|
175
+ action = @topic_manager.retrieve_action(topics[0])
176
+ @is_subscribed[action] = false if action.nil?
177
+ end
178
+ end
179
+
180
+ def register_token_callback(token, callback, &block)
181
+ if callback.is_a?(Proc)
182
+ @token_callback[token] = callback
183
+ elsif block_given?
184
+ @token_callback[token] = block
185
+ end
186
+ end
187
+
188
+ def remove_token_callback(token)
189
+ @token_callback.delete(token)
190
+ end
191
+
192
+ def register_action_callback(action, callback, &block)
193
+ if callback.is_a?(Proc)
194
+ @topic_subscribed_callback[action] = callback
195
+ elsif block_given?
196
+ @topic_subscribed_callback[action] = block
197
+ end
198
+ end
199
+
200
+ def remove_action_callback(action)
201
+ @topic_subscribed_callback[action] = nil
202
+ end
203
+
204
+ def decresase_task_count(action)
205
+ @topic_subscribed_task_count[action] -= 1
206
+ if @topic_subscribed_task_count[action] <= 0
207
+ @topic_subscribed_task_count[action] = 0
208
+ unless @persistent_subscribe
209
+ @topic_manager.shadow_topic_unsubscribe(action.to_s)
210
+ @is_subscribed[action] = false
211
+ end
212
+ end
213
+ end
214
+
215
+ ### The default callback that is called by every actions
216
+ ### It acknowledge the accepted status if action success
217
+ ### Call a specific callback for each actions if it defined have been register previously
218
+ def do_message_callback(message)
219
+ topic = message.topic
220
+ action = parse_action(topic)
221
+ type = parse_type(topic)
222
+ payload = message.payload
223
+ token = nil
224
+ new_version = -1
225
+ @parser_mutex.synchronize() {
226
+ @payload_parser.set_message(payload)
227
+ new_version = @payload_parser.get_attribute_value("version")
228
+ token = @payload_parser.get_attribute_value("clientToken")
229
+ }
230
+ if %w(get update delete).include?(action)
231
+ if @token_pool.has_key?(token)
232
+ @token_pool[token].cancel
233
+ @token_pool.delete(token)
234
+ if type.eql?("accepted")
235
+ do_accepted(message, action.to_sym, token, type, new_version)
236
+ else
237
+ do_rejected(token, action, new_version)
238
+ end
239
+ @task_count_mutex.synchronize {
240
+ decresase_task_count(action.to_sym)
241
+ }
242
+ end
243
+ elsif %w(delta).include?(action)
244
+ do_delta(message, new_version)
245
+ end
246
+ end
247
+
248
+ def do_accepted(message, action, token, type, new_version)
249
+ if new_version && new_version >= @last_stable_version
250
+ @logger.info("The #{action} action with the token #{token} have been accepted.") if logger?
251
+ type.eql?("delete") ? @last_stable_version = -1 : @last_stable_version = new_version
252
+ Thread.new do
253
+ accepted_tasks(message, action, token)
254
+ end
255
+ else
256
+ @logger.warn("CATCH AN ACCEPTED #{action} BUT OUTDATED/INVALID VERSION (= #{new_version})\n") if logger?
257
+ end
258
+ end
259
+
260
+ def accepted_tasks(message, action, token)
261
+ @topic_subscribed_callback[action].call(message) unless @topic_subscribed_callback[action].nil?
262
+ @token_callback[token].call(message) if @token_callback.has_key?(token)
263
+ @token_callback.delete(token)
264
+ end
265
+
266
+ def do_rejected(token, action, new_version)
267
+ if new_version && new_version >= @last_stable_version
268
+ @logger.info("The #{action} action with the token #{token} have been rejected.") if logger?
269
+ @token_callback.delete(token)
270
+ else
271
+ @logger.warn("CATCH AN REJECTED #{action} BUT OUTDATED/INVALID VERSION (= #{new_version})\n") if logger?
272
+ end
273
+ end
274
+
275
+ def do_delta(message, new_version)
276
+ if new_version && new_version >= @last_stable_version
277
+ @logger.info("A delta action have been accepted.") if logger?
278
+ @last_stable_version = new_version
279
+ Thread.new { @topic_subscribed_callback[:delta].call(message) } unless @topic_subscribed_callback[:delta].nil?
280
+ else
281
+ @logger.warn("CATCH A DELTA BUT OUTDATED/INVALID VERSION (= #{new_version})\n") if logger?
282
+ end
283
+ end
284
+
285
+ def handle_subscription(action, timeout)
286
+ @topic_manager.shadow_topic_subscribe(action.to_s, @default_callback)
287
+ if @topic_manager.paho_client?
288
+ ref = Time.now + timeout
289
+ while !@is_subscribed[action] && handle_timeout(ref) do
290
+ sleep 0.0001
291
+ end
292
+ else
293
+ sleep 2
294
+ end
295
+ end
296
+
297
+ def handle_timeout(ref)
298
+ Time.now <= ref
299
+ end
300
+
301
+ def parse_shadow_name(topic)
302
+ topic.split('/')[2]
303
+ end
304
+
305
+ def parse_action(topic)
306
+ if topic.split('/')[5] == "delta"
307
+ topic.split('/')[5]
308
+ else
309
+ topic.split('/')[4]
310
+ end
311
+ end
312
+
313
+ def parse_type(topic)
314
+ topic.split('/')[5]
315
+ end
316
+ end
317
+ end
318
+ end