apiotics_aws_client 1.0.1

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.
Files changed (36) 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.gemspec +40 -0
  9. data/bin/console +11 -0
  10. data/bin/setup +7 -0
  11. data/codeclimate.yml +6 -0
  12. data/lib/aws_iot_device.rb +8 -0
  13. data/lib/aws_iot_device/mqtt_adapter.rb +31 -0
  14. data/lib/aws_iot_device/mqtt_adapter/client.rb +207 -0
  15. data/lib/aws_iot_device/mqtt_adapter/paho_mqtt_adapter.rb +207 -0
  16. data/lib/aws_iot_device/mqtt_adapter/ruby_mqtt_adapter.rb +183 -0
  17. data/lib/aws_iot_device/mqtt_shadow_client.rb +7 -0
  18. data/lib/aws_iot_device/mqtt_shadow_client/json_payload_parser.rb +34 -0
  19. data/lib/aws_iot_device/mqtt_shadow_client/mqtt_manager.rb +201 -0
  20. data/lib/aws_iot_device/mqtt_shadow_client/shadow_action_manager.rb +318 -0
  21. data/lib/aws_iot_device/mqtt_shadow_client/shadow_client.rb +118 -0
  22. data/lib/aws_iot_device/mqtt_shadow_client/shadow_topic_manager.rb +75 -0
  23. data/lib/aws_iot_device/mqtt_shadow_client/token_creator.rb +36 -0
  24. data/lib/aws_iot_device/mqtt_shadow_client/topic_builder.rb +35 -0
  25. data/lib/aws_iot_device/version.rb +3 -0
  26. data/samples/config_shadow.rb +72 -0
  27. data/samples/mqtt_client_samples/mqtt_client_samples.rb +38 -0
  28. data/samples/shadow_action_samples/sample_shadow_action_update.rb +25 -0
  29. data/samples/shadow_client_samples/samples_shadow_client_block.rb +25 -0
  30. data/samples/shadow_client_samples/samples_shadow_client_delete.rb +24 -0
  31. data/samples/shadow_client_samples/samples_shadow_client_description.rb +64 -0
  32. data/samples/shadow_client_samples/samples_shadow_client_get.rb +23 -0
  33. data/samples/shadow_client_samples/samples_shadow_client_getting_started.rb +22 -0
  34. data/samples/shadow_client_samples/samples_shadow_client_update.rb +30 -0
  35. data/samples/shadow_topic_samples/sample_topic_manager.rb +26 -0
  36. metadata +216 -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