aws_iot_device 0.1.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/README.md +155 -148
  4. data/aws_iot_device.gemspec +11 -11
  5. data/codeclimate.yml +6 -0
  6. data/lib/aws_iot_device.rb +2 -1
  7. data/lib/aws_iot_device/mqtt_adapter.rb +1 -2
  8. data/lib/aws_iot_device/mqtt_adapter/client.rb +74 -6
  9. data/lib/aws_iot_device/mqtt_adapter/paho_mqtt_adapter.rb +207 -0
  10. data/lib/aws_iot_device/mqtt_adapter/ruby_mqtt_adapter.rb +10 -4
  11. data/lib/aws_iot_device/mqtt_shadow_client.rb +1 -0
  12. data/lib/aws_iot_device/mqtt_shadow_client/mqtt_manager.rb +133 -67
  13. data/lib/aws_iot_device/mqtt_shadow_client/shadow_action_manager.rb +229 -146
  14. data/lib/aws_iot_device/mqtt_shadow_client/shadow_client.rb +78 -20
  15. data/lib/aws_iot_device/mqtt_shadow_client/shadow_topic_manager.rb +51 -26
  16. data/lib/aws_iot_device/mqtt_shadow_client/topic_builder.rb +15 -30
  17. data/lib/aws_iot_device/version.rb +1 -1
  18. data/samples/config_shadow.rb +72 -0
  19. data/samples/mqtt_client_samples/mqtt_client_samples.rb +7 -59
  20. data/samples/shadow_action_samples/sample_shadow_action_update.rb +7 -61
  21. data/samples/shadow_client_samples/samples_shadow_client_block.rb +25 -0
  22. data/samples/shadow_client_samples/samples_shadow_client_delete.rb +9 -58
  23. data/samples/shadow_client_samples/samples_shadow_client_description.rb +64 -0
  24. data/samples/shadow_client_samples/samples_shadow_client_get.rb +11 -62
  25. data/samples/shadow_client_samples/samples_shadow_client_getting_started.rb +22 -0
  26. data/samples/shadow_client_samples/samples_shadow_client_update.rb +7 -58
  27. data/samples/shadow_topic_samples/sample_topic_manager.rb +10 -61
  28. metadata +71 -16
  29. data/lib/aws_iot_device/mqtt_adapter/mqtt_adapter.rb +0 -139
@@ -9,19 +9,21 @@ module AwsIotDevice
9
9
  module MqttShadowClient
10
10
  class ShadowActionManager
11
11
  ### This the main AWS action manager
12
- ### It allows to execute the AWS actions (get, update, delete)
13
- ### It allows to manage the time out after an action have been start
14
- ### Actions request are send on the general actions topic and answer is retreived from accepted/refused/delta topics
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
15
 
16
- def initialize(shadow_name, shadow_topic_manager, persistent_subscribe=false)
16
+ attr_accessor :logger
17
+
18
+ def initialize(shadow_name, mqtt_client, persistent_subscribe=false)
17
19
  @shadow_name = shadow_name
18
- @topic_manager = shadow_topic_manager
20
+ @topic_manager = ShadowTopicManager.new(mqtt_client, shadow_name)
19
21
  @payload_parser = JSONPayloadParser.new
20
22
  @is_subscribed = {}
21
23
  @is_subscribed[:get] = false
22
24
  @is_subscribed[:update] = false
23
25
  @is_subscribed[:delete] = false
24
- @token_handler = TokenCreator.new(shadow_name, shadow_topic_manager.client_id)
26
+ @token_handler = TokenCreator.new(shadow_name, mqtt_client.client_id)
25
27
  @persistent_subscribe = persistent_subscribe
26
28
  @last_stable_version = -1 #Mean no currentely stable
27
29
  @topic_subscribed_callback = {}
@@ -33,78 +35,11 @@ module AwsIotDevice
33
35
  @topic_subscribed_task_count[:update] = 0
34
36
  @topic_subscribed_task_count[:delete] = 0
35
37
  @token_pool = {}
36
- @general_action_mutex = Mutex.new
37
- @default_callback = Proc.new do |message|
38
- do_default_callback(message)
39
- end
40
- end
41
-
42
- ### The default callback that is called by every actions
43
- ### It acknowledge the accepted status if action success
44
- ### Call a specific callback for each actions if it defined have been register previously
45
- def do_default_callback(message)
46
- @general_action_mutex.synchronize(){
47
- topic = message.topic
48
- action = parse_action(topic)
49
- type = parse_type(topic)
50
- payload = message.payload
51
- @payload_parser.set_message(payload)
52
- if %w(get update delete).include?(action)
53
- token = @payload_parser.get_attribute_value("clientToken")
54
- if @token_pool.has_key?(token)
55
- if type.eql?("accepted")
56
- new_version = @payload_parser.get_attribute_value("version")
57
- if new_version && new_version >= @last_stable_version
58
- type.eql?("delete") ? @last_stable_version = -1 : @last_stable_version = new_version
59
- Thread.new { @topic_subscribed_callback[action.to_sym].call(message) } unless @topic_subscribed_callback[action.to_sym].nil?
60
- else
61
- puts "CATCH AN UPDATE BUT OUTDATED/INVALID VERSION (= #{new_version}) FOR TOKEN #{token}\n"
62
- end
63
- end
64
- @token_pool[token].cancel
65
- @token_pool.delete(token)
66
- @topic_subscribed_task_count[action.to_sym] -= 1
67
- if @topic_subscribed_task_count[action.to_sym] <= 0
68
- @topic_subscribed_task_count[action.to_sym] = 0
69
- unless @persistent_subscribe
70
- @topic_manager.shadow_topic_unsubscribe(@shadow_name, action)
71
- @is_subscribed[action.to_sym] = false
72
- end
73
- end
74
- end
75
- elsif %w(delta).include?(action)
76
- new_version = @payload_parser.get_attribute_value("version")
77
- if new_version && new_version >= @last_stable_version
78
- @last_stable_version = new_version
79
- Thread.new { @topic_subscribed_callback[action.to_sym].call(message) } if @topic_subscribed_callback[action.to_sym]
80
- else
81
- puts "CATCH A DELTA BUT OUTDATED/INVALID VERSION (= #{new_version})\n"
82
- end
83
- end
84
- }
85
- end
86
-
87
- ### Should cancel the token after a preset time interval
88
- def timeout_manager(action_name, token)
89
- @general_action_mutex.synchronize(){
90
- if @token_pool.has_key?(token)
91
- action = action_name.to_sym
92
- @token_pool.delete(token)
93
- puts "The #{action_name} request with the token #{token} has timed out!\n"
94
- @topic_subscribed_task_count[action] -= 1
95
- unless @topic_subscribed_task_count[action] <= 0
96
- @topic_subscribed_task_count[action] = 0
97
- unless @persistent_subscribe
98
- @topic_manager.shadow_topic_unsubscribe(@shadow_name, action)
99
- @is_subscribed[action.to_sym] = false
100
- end
101
- end
102
- unless @topic_subscribed_callback[action].blank?
103
- puts "Shadow request with token: #{token} has timed out."
104
- @topic_subscribed_callback[action].call("REQUEST TIME OUT", "timeout", token)
105
- end
106
- end
107
- }
38
+ @token_callback = {}
39
+ @task_count_mutex = Mutex.new
40
+ @token_mutex = Mutex.new
41
+ @parser_mutex = Mutex.new
42
+ set_basic_callback
108
43
  end
109
44
 
110
45
  ### Send and publish packet with an empty payload contains in a valid JSON format.
@@ -128,92 +63,240 @@ module AwsIotDevice
128
63
  ### Returns :
129
64
  ### > the token associate to the current action (which also store in @token_pool)
130
65
 
131
- def shadow_get(callback=nil, timeout=5)
132
- current_token = ""
133
- json_payload = ""
134
- timer = Timers::Group.new
135
- @general_action_mutex.synchronize(){
136
- @topic_subscribed_callback[:get] = callback
137
- @topic_subscribed_task_count[:get] += 1
138
- current_token = @token_handler.create_next_token
139
- timer.after(timeout){ timeout_manager(:get, current_token) }
140
- @payload_parser.set_attribute_value("clientToken",current_token)
141
- json_payload = @payload_parser.get_json
142
- unless @is_subscribed[:get]
143
- @topic_manager.shadow_topic_subscribe(@shadow_name, "get", @default_callback)
144
- @is_subscribed[:get] = true
145
- end
146
- @topic_manager.shadow_topic_publish(@shadow_name, "get", json_payload)
147
- @token_pool[current_token] = timer
148
- Thread.new{ timer.wait }
149
- current_token
150
- }
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)
151
109
  end
152
110
 
153
- def shadow_update(payload, callback, timeout)
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)
154
123
  current_token = Symbol
155
124
  timer = Timers::Group.new
156
125
  json_payload = ""
157
- @general_action_mutex.synchronize(){
158
- if callback.is_a?(Proc)
159
- @topic_subscribed_callback[:update] = callback
160
- end
161
- @topic_subscribed_task_count[:update] += 1
126
+ @token_mutex.synchronize(){
162
127
  current_token = @token_handler.create_next_token
163
- timer.after(timeout){ timeout_manager(:update, current_token) }
164
- @payload_parser.set_message(payload)
165
- @payload_parser.set_attribute_value("clientToken",current_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)
166
133
  json_payload = @payload_parser.get_json
167
- unless @is_subscribed[:update]
168
- @topic_manager.shadow_topic_subscribe(@shadow_name, "update", @default_callback)
169
- @is_subscribed[:update] = true
170
- end
171
- @topic_manager.shadow_topic_publish(@shadow_name, "update", json_payload)
172
- @token_pool[current_token] = timer
173
- Thread.new{ timer.wait }
174
- current_token
175
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
176
144
  end
177
145
 
178
- def shadow_delete(callback, timeout)
179
- current_token = Symbol
180
- timer = Timers::Group.new
181
- json_payload = ""
182
- @general_action_mutex.synchronize(){
183
- if callback.is_a?(Proc)
184
- @topic_subscribed_callback[:delete] = callback
185
- end
186
- @topic_subscribed_task_count[:delete] += 1
187
- current_token = @token_handler.create_next_token
188
- timer.after(timeout){ timeout_manager(:delete, current_token) }
189
- @payload_parser.set_attribute_value("clientToken",current_token)
190
- json_payload = @payload_parser.get_json
191
- unless @is_subscribed[:delete]
192
- @topic_manager.shadow_topic_subscribe(@shadow_name, "delete", @default_callback)
193
- @is_subscribed[:delete] = true
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
194
211
  end
195
- @topic_manager.shadow_topic_publish(@shadow_name, "delete", json_payload)
196
- @token_pool[current_token] = timer
197
- Thread.new{ timer.wait }
198
- current_token
199
- }
212
+ end
200
213
  end
201
214
 
202
- def register_shadow_delta_callback(callback)
203
- @general_action_mutex.synchronize(){
204
- @topic_subscribed_callback[:delta] = callback
205
- @topic_manager.shadow_topic_subscribe(@shadow_name, "delta", @default_callback)
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")
206
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
207
246
  end
208
247
 
209
- def remove_shadow_delta_callback
210
- @general_action_mutex.synchronize(){
211
- @topic_subscribe_callback.delete[:delta]
212
- @topic_manager.shadow_topic_unsubscribe(@shadow_name, "delta")
213
- }
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
214
258
  end
215
259
 
216
- private
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
217
300
 
218
301
  def parse_shadow_name(topic)
219
302
  topic.split('/')[2]
@@ -5,56 +5,114 @@ require 'aws_iot_device/mqtt_shadow_client/shadow_action_manager'
5
5
  module AwsIotDevice
6
6
  module MqttShadowClient
7
7
  class ShadowClient
8
- attr_accessor :action_manager
9
8
 
10
- def initialize
11
- @mqtt_client = MqttManager.new
9
+ def initialize(*args)
10
+ unless args.last.nil?
11
+ config_attr(args.last)
12
+ else
13
+ @mqtt_client = MqttManager.new
14
+ end
12
15
  end
13
16
 
14
- def connect
15
- @mqtt_client.connect
17
+ def connect(*args, &block)
18
+ @mqtt_client.connect(*args)
19
+ self.logger.info("Connected to the AWS IoT platform") if logger?
20
+ if block_given?
21
+ begin
22
+ yield(self)
23
+ ensure
24
+ @mqtt_client.disconnect
25
+ end
26
+ end
16
27
  end
17
28
 
18
- def topic_manager
19
- @topic_manager = ShadowTopicManager.new(@mqtt_client)
29
+ def create_shadow_handler_with_name(shadow_name, persistent_subscribe=false)
30
+ @action_manager = ShadowActionManager.new(shadow_name, @mqtt_client, persistent_subscribe)
20
31
  end
21
32
 
22
- def create_shadow_handler_with_name(shadow_name, is_persistent_subscribe=false)
23
- topic_manager
24
- @action_manager = ShadowActionManager.new(shadow_name, @topic_manager, is_persistent_subscribe)
33
+ def logger=(logger_path)
34
+ file = File.open(logger_path, "a+")
35
+ log_file = Logger.new(file)
36
+ log_file.level = Logger::DEBUG
37
+ @action_manager.logger = log_file
25
38
  end
26
39
 
27
- def get_shadow(callback=nil, timeout=5)
28
- @action_manager.shadow_get(callback, timeout)
40
+ def logger
41
+ @action_manager.logger
29
42
  end
30
43
 
31
- def update_shadow(payload, callback=nil, timeout=5)
32
- @action_manager.shadow_update(payload, callback, timeout)
44
+ def logger?
45
+ @action_manager.logger?
33
46
  end
34
47
 
35
- def delete_shadow(callback=nil, timeout=5)
36
- @action_manager.shadow_delete(callback, timeout)
48
+ def get_shadow(timeout=5, callback=nil, &block)
49
+ @action_manager.shadow_get(timeout, callback, &block)
37
50
  end
38
51
 
39
- def register_delta_callback(callback)
40
- @action_manager.register_shadow_delta_callback(callback)
52
+ def update_shadow(payload, timeout=5, callback=nil, &block)
53
+ @action_manager.shadow_update(payload, timeout, callback, &block)
41
54
  end
42
55
 
43
- def remove_shadow_delta_callback
56
+ def delete_shadow(timeout=5, callback=nil, &block)
57
+ @action_manager.shadow_delete(timeout, callback, &block)
58
+ end
59
+
60
+ def register_get_callback(callback=nil, &block)
61
+ @action_manager.register_get_callback(callback, &block)
62
+ end
63
+
64
+ def register_update_callback(callback=nil, &block)
65
+ @action_manager.register_update_callback(callback, &block)
66
+ end
67
+
68
+ def register_delete_callback(callback=nil, &block)
69
+ @action_manager.register_delete_callback(callback, &block)
70
+ end
71
+
72
+ def register_delta_callback(callback=nil, &block)
73
+ @action_manager.register_shadow_delta_callback(callback, &block)
74
+ end
75
+
76
+ def remove_delta_callback
44
77
  @action_manager.remove_shadow_delta_callback
45
78
  end
46
79
 
80
+ def remove_get_callback
81
+ @action_manager.remove_get_callback
82
+ end
83
+
84
+ def remove_update_callback
85
+ @action_manager.remove_update_callback
86
+ end
87
+
88
+ def remove_delete_callback
89
+ @action_manager.remove_delete_callback
90
+ end
91
+
47
92
  def disconnect
48
93
  @mqtt_client.disconnect
49
94
  end
50
95
 
51
- def configure_endpoint(host,port)
96
+ def configure_endpoint(host, port)
52
97
  @mqtt_client.config_endpoint(host,port)
53
98
  end
54
99
 
55
100
  def configure_credentials(ca_file, key, cert)
56
101
  @mqtt_client.config_ssl_context(ca_file, key, cert)
57
102
  end
103
+
104
+
105
+ private
106
+
107
+ def config_attr(args)
108
+ shadow_attr = args.dup
109
+ shadow_attr.keep_if {|key| key == :shadow_name || key == :persistent_subscribe || key == :logger }
110
+ mqtt_attr = args
111
+ mqtt_attr.delete_if {|key| key == :shadow_name || key == :persistent_subscribe || key == :logger }
112
+ @mqtt_client = MqttManager.new(mqtt_attr)
113
+ shadow_attr[:persistent_subscribe] ||= false
114
+ @action_manager = create_shadow_handler_with_name(shadow_attr[:shadow_name], shadow_attr[:persistent_subsrcibe]) if shadow_attr.has_key?(:shadow_name)
115
+ end
58
116
  end
59
117
  end
60
118
  end