apiotics_aws_iot_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.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/README.md +207 -0
- data/Rakefile +6 -0
- data/apiotics-aws-iot-client-1.0.0.gem +0 -0
- data/apiotics_aws_client-1.0.1.gem +0 -0
- data/apiotics_aws_iot_client.gemspec +40 -0
- data/bin/console +11 -0
- data/bin/setup +7 -0
- data/codeclimate.yml +6 -0
- data/lib/aws_iot_device.rb +8 -0
- data/lib/aws_iot_device/mqtt_adapter.rb +31 -0
- data/lib/aws_iot_device/mqtt_adapter/client.rb +207 -0
- data/lib/aws_iot_device/mqtt_adapter/paho_mqtt_adapter.rb +207 -0
- data/lib/aws_iot_device/mqtt_adapter/ruby_mqtt_adapter.rb +183 -0
- data/lib/aws_iot_device/mqtt_shadow_client.rb +7 -0
- data/lib/aws_iot_device/mqtt_shadow_client/json_payload_parser.rb +34 -0
- data/lib/aws_iot_device/mqtt_shadow_client/mqtt_manager.rb +201 -0
- data/lib/aws_iot_device/mqtt_shadow_client/shadow_action_manager.rb +318 -0
- data/lib/aws_iot_device/mqtt_shadow_client/shadow_client.rb +118 -0
- data/lib/aws_iot_device/mqtt_shadow_client/shadow_topic_manager.rb +75 -0
- data/lib/aws_iot_device/mqtt_shadow_client/token_creator.rb +36 -0
- data/lib/aws_iot_device/mqtt_shadow_client/topic_builder.rb +35 -0
- data/lib/aws_iot_device/version.rb +3 -0
- data/samples/config_shadow.rb +72 -0
- data/samples/mqtt_client_samples/mqtt_client_samples.rb +38 -0
- data/samples/shadow_action_samples/sample_shadow_action_update.rb +25 -0
- data/samples/shadow_client_samples/samples_shadow_client_block.rb +25 -0
- data/samples/shadow_client_samples/samples_shadow_client_delete.rb +24 -0
- data/samples/shadow_client_samples/samples_shadow_client_description.rb +64 -0
- data/samples/shadow_client_samples/samples_shadow_client_get.rb +23 -0
- data/samples/shadow_client_samples/samples_shadow_client_getting_started.rb +22 -0
- data/samples/shadow_client_samples/samples_shadow_client_update.rb +30 -0
- data/samples/shadow_topic_samples/sample_topic_manager.rb +26 -0
- metadata +217 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'aws_iot_device/mqtt_shadow_client/mqtt_manager'
|
2
|
+
require 'aws_iot_device/mqtt_shadow_client/shadow_topic_manager'
|
3
|
+
require 'aws_iot_device/mqtt_shadow_client/shadow_action_manager'
|
4
|
+
|
5
|
+
module AwsIotDevice
|
6
|
+
module MqttShadowClient
|
7
|
+
class ShadowClient
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
unless args.last.nil?
|
11
|
+
config_attr(args.last)
|
12
|
+
else
|
13
|
+
@mqtt_client = MqttManager.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
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
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_shadow_handler_with_name(shadow_name, persistent_subscribe=false)
|
30
|
+
@action_manager = ShadowActionManager.new(shadow_name, @mqtt_client, persistent_subscribe)
|
31
|
+
end
|
32
|
+
|
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
|
38
|
+
end
|
39
|
+
|
40
|
+
def logger
|
41
|
+
@action_manager.logger
|
42
|
+
end
|
43
|
+
|
44
|
+
def logger?
|
45
|
+
@action_manager.logger?
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_shadow(timeout=5, callback=nil, &block)
|
49
|
+
@action_manager.shadow_get(timeout, callback, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_shadow(payload, timeout=5, callback=nil, &block)
|
53
|
+
@action_manager.shadow_update(payload, timeout, callback, &block)
|
54
|
+
end
|
55
|
+
|
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
|
77
|
+
@action_manager.remove_shadow_delta_callback
|
78
|
+
end
|
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
|
+
|
92
|
+
def disconnect
|
93
|
+
@mqtt_client.disconnect
|
94
|
+
end
|
95
|
+
|
96
|
+
def configure_endpoint(host, port)
|
97
|
+
@mqtt_client.config_endpoint(host,port)
|
98
|
+
end
|
99
|
+
|
100
|
+
def configure_credentials(ca_file, key, cert)
|
101
|
+
@mqtt_client.config_ssl_context(ca_file, key, cert)
|
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
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'aws_iot_device/mqtt_shadow_client/topic_builder'
|
2
|
+
|
3
|
+
module AwsIotDevice
|
4
|
+
module MqttShadowClient
|
5
|
+
class ShadowTopicManager
|
6
|
+
|
7
|
+
def initialize(mqtt_manager, shadow_name)
|
8
|
+
raise ArgumentError, "topic manager should be initialized with a mqtt_manager but was undefined" if mqtt_manager.nil?
|
9
|
+
raise ArgumentError, "topic manager should be initialize with a mqtt mmanager but was undefined" if shadow_name.nil?
|
10
|
+
|
11
|
+
@mqtt_manager = mqtt_manager
|
12
|
+
@sub_unsub_mutex = Mutex.new()
|
13
|
+
@topic = TopicBuilder.new(shadow_name)
|
14
|
+
@timeout = mqtt_manager.mqtt_operation_timeout_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def shadow_topic_publish(action, payload)
|
18
|
+
@mqtt_manager.publish(@topic.get_topic_general(action), payload, false, 0)
|
19
|
+
end
|
20
|
+
|
21
|
+
def shadow_topic_subscribe(action, callback=nil)
|
22
|
+
@sub_unsub_mutex.synchronize() {
|
23
|
+
@subacked = false
|
24
|
+
if @topic.is_delta?(action)
|
25
|
+
@mqtt_manager.subscribe(@topic.get_topic_delta, 0, callback)
|
26
|
+
else
|
27
|
+
@mqtt_manager.subscribe_bunch([@topic.get_topic_accepted(action), 1, callback], [@topic.get_topic_rejected(action), 1, callback])
|
28
|
+
end
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def shadow_topic_unsubscribe(action)
|
33
|
+
@sub_unsub_mutex.synchronize(){
|
34
|
+
@unsubacked = false
|
35
|
+
if @topic.is_delta?(action)
|
36
|
+
@mqtt_manager.unsubscribe(@topic.get_topic_delta)
|
37
|
+
else
|
38
|
+
@mqtt_manager.unsubscribe_bunch(@topic.get_topic_accepted(action), @topic.get_topic_rejected(action))
|
39
|
+
end
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def retrieve_action(topic)
|
44
|
+
res = nil
|
45
|
+
ACTION_NAME.each do |action|
|
46
|
+
if topic[0] == @topic.get_topic_accepted(action)
|
47
|
+
res = action.to_sym
|
48
|
+
break
|
49
|
+
end
|
50
|
+
end
|
51
|
+
res
|
52
|
+
end
|
53
|
+
|
54
|
+
def paho_client?
|
55
|
+
@mqtt_manager.paho_client?
|
56
|
+
end
|
57
|
+
|
58
|
+
def on_suback=(callback)
|
59
|
+
@mqtt_manager.on_suback = callback
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_suback(&block)
|
63
|
+
@mqtt_manager.on_suback(&block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def on_unsuback=(callback)
|
67
|
+
@mqtt_manager.on_unsuback = callback
|
68
|
+
end
|
69
|
+
|
70
|
+
def on_unsuback(&block)
|
71
|
+
@mqtt_manager.on_unsuback(&block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'timers'
|
3
|
+
require 'thread'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module AwsIotDevice
|
7
|
+
module MqttShadowClient
|
8
|
+
class TokenCreator
|
9
|
+
### This class manage the clients token.
|
10
|
+
### Every actions receive a token for a certian interval, meaning that action is waiting to be proceed.
|
11
|
+
### When token time run out or the actions have been treated token should deleted.
|
12
|
+
|
13
|
+
def initialize(shadow_name, client_id)
|
14
|
+
if shadow_name.length > 16
|
15
|
+
@shadow_name = shadow_name[0..15]
|
16
|
+
else
|
17
|
+
@shadow_name = shadow_name
|
18
|
+
end
|
19
|
+
@client_id = client_id
|
20
|
+
@sequence_number = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_next_token
|
24
|
+
token = ""
|
25
|
+
token << "#{@client_id}" << "_" << "#{@shadow_name}" << "_" << "#{@sequence_number}" << "_" << "#{random_token_string(5)}"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def random_token_string(lenght)
|
31
|
+
charset = Array('A'..'Z') + Array('a'..'z') + Array('0'..'9')
|
32
|
+
Array.new(lenght) { charset.sample }.join
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module AwsIotDevice
|
2
|
+
module MqttShadowClient
|
3
|
+
class TopicBuilder
|
4
|
+
def initialize(shadow_name)
|
5
|
+
raise ArgumentError, "topic_builder initialization, shadow_name is required but undefined" if shadow_name.nil?
|
6
|
+
|
7
|
+
@shadow_name = shadow_name
|
8
|
+
|
9
|
+
@topic_delta = "$aws/things/#{shadow_name}/shadow/update/delta"
|
10
|
+
@topic_general = "$aws/things/#{shadow_name}/shadow/"
|
11
|
+
end
|
12
|
+
|
13
|
+
def is_delta?(action)
|
14
|
+
action == ACTION_NAME[3]
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_topic_general(action)
|
18
|
+
raise ArgumentError, "topic_builder, get topic, unreconized action_name \"#{action}\"" unless ACTION_NAME.include?(action)
|
19
|
+
@topic_general + action
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_topic_accepted(action)
|
23
|
+
get_topic_general(action) + "/accepted"
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_topic_rejected(action)
|
27
|
+
get_topic_general(action) + "/rejected"
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_topic_delta
|
31
|
+
@topic_delta
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
options = {}
|
4
|
+
|
5
|
+
OptionParser.new do |opts|
|
6
|
+
opts.banner = "Basic usage basic_greeting.rb -c \"YOUR_CERTIFICATE_PATH\" -k \"YOUR_KEY_FILE_PATH\" -ca \"YOUR_ROOT_CA_PATH -H \"YOUR_ENDPOINT\" -p 8883 -t \"YOUR THING NAME\"\n"
|
7
|
+
|
8
|
+
opts.separator ""
|
9
|
+
opts.separator "Common options"
|
10
|
+
opts.on_tail("-h", "--help", "--usage", "Show this message") do
|
11
|
+
puts opts
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
|
15
|
+
opts.on("-H", "--host [END_POINT]", "The endpoint where you want to connect") do |host|
|
16
|
+
options[:host] = host
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on("-p", "--port [MQTT_PORT]", "The port used for the connection Default is 8883") do |port|
|
20
|
+
options[:port] = port
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-c", "--cert [CERT_FILE_PATH]", "The path to the certificate file of the private key.") do |cert|
|
24
|
+
options[:cert] = cert
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-k", "--key [KEY_FILE_PATH]", "The path to private key file that would be used for encryption") do |key|
|
28
|
+
options[:key] = key
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-a", "--root_ca [ROOT_CA_PATH]", "The path to the authority certification file") do |root_ca|
|
32
|
+
options[:root_ca] = root_ca
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-t", "--thing [THING_NAME]", "The Thing name on which the action would be done") do |thing|
|
36
|
+
options[:things] = thing
|
37
|
+
end
|
38
|
+
end.parse!(ARGV)
|
39
|
+
|
40
|
+
@host = options[:host]
|
41
|
+
@port = options[:port] || 8883
|
42
|
+
@certificate_path = options[:cert]
|
43
|
+
@private_key_path = options[:key]
|
44
|
+
@root_ca_path = options[:root_ca]
|
45
|
+
@thing = options[:things]
|
46
|
+
|
47
|
+
def setting_mqtt_client
|
48
|
+
mqtt_client = AwsIotDevice::MqttShadowClient::MqttManager.new(host: @host,
|
49
|
+
port: @port,
|
50
|
+
ssl: true)
|
51
|
+
mqtt_client.config_ssl_context(@root_ca_path, @private_key_path, @certificate_path)
|
52
|
+
mqtt_client.connect
|
53
|
+
mqtt_client
|
54
|
+
end
|
55
|
+
|
56
|
+
def setting_topic_manager(mqtt_client)
|
57
|
+
topic_manager = AwsIotDevice::MqttShadowClient::ShadowTopicManager.new(mqtt_client, @thing)
|
58
|
+
end
|
59
|
+
|
60
|
+
def setting_action_manager(mqtt_client)
|
61
|
+
action_manager = AwsIotDevice::MqttShadowClient::ShadowActionManager.new(@thing, mqtt_client, false)
|
62
|
+
action_manager
|
63
|
+
end
|
64
|
+
|
65
|
+
def setting_shadow
|
66
|
+
shadow_client = AwsIotDevice::MqttShadowClient::ShadowClient.new
|
67
|
+
shadow_client.configure_endpoint(@host, @port)
|
68
|
+
shadow_client.configure_credentials(@root_ca_path, @private_key_path, @certificate_path)
|
69
|
+
shadow_client.create_shadow_handler_with_name(@thing, true)
|
70
|
+
shadow_client.connect
|
71
|
+
shadow_client
|
72
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
$:.unshift(File.expand_path("../../../samples", __FILE__))
|
2
|
+
|
3
|
+
require 'aws_iot_device'
|
4
|
+
require 'config_shadow'
|
5
|
+
|
6
|
+
client = setting_mqtt_client
|
7
|
+
|
8
|
+
client2 = setting_mqtt_client
|
9
|
+
|
10
|
+
callback = Proc.new do |message|
|
11
|
+
p " Client1 catch message event"
|
12
|
+
p "--- Topic: #{message.topic}"
|
13
|
+
p "--- Payload: #{message.payload}"
|
14
|
+
end
|
15
|
+
|
16
|
+
callback2 = Proc.new do |message|
|
17
|
+
p " Client2 catch message event"
|
18
|
+
p "--- Topic: #{message.topic}"
|
19
|
+
p "--- Payload: #{message.payload}"
|
20
|
+
end
|
21
|
+
|
22
|
+
client.subscribe("topic_2", 0, callback)
|
23
|
+
client2.subscribe("topic_1", 0,callback2)
|
24
|
+
|
25
|
+
puts "# STARTING EXAMPLE #"
|
26
|
+
client.publish("topic_1", "Hello Sir. My name is client 1. How do you do? ")
|
27
|
+
client2.publish("topic_2", "Hello Mister Client 1. My name is client 2. How do you do?")
|
28
|
+
sleep 1
|
29
|
+
|
30
|
+
2.times do
|
31
|
+
client.publish("topic_1", "How do you do?")
|
32
|
+
sleep 1
|
33
|
+
client2.publish("topic_2", "How do you do?")
|
34
|
+
sleep 1
|
35
|
+
end
|
36
|
+
sleep 2
|
37
|
+
client.disconnect
|
38
|
+
client2.disconnect
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.unshift(File.expand_path("../../../samples", __FILE__))
|
2
|
+
|
3
|
+
require 'aws_iot_device'
|
4
|
+
require 'config_shadow'
|
5
|
+
|
6
|
+
filter_callback = Proc.new do |message|
|
7
|
+
puts "Executing the specific callback for topic: #{message.topic}\n##########################################\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
mqtt_client = setting_mqtt_client
|
11
|
+
client = setting_action_manager(mqtt_client)
|
12
|
+
|
13
|
+
client.register_shadow_delta_callback(filter_callback)
|
14
|
+
timeout = 5
|
15
|
+
|
16
|
+
n = 1
|
17
|
+
5.times do
|
18
|
+
json_payload = "{\"state\":{\"desired\":{\"property\":\"RubySDK\",\"count\":#{n}}}}"
|
19
|
+
client.shadow_update(json_payload, timeout, filter_callback)
|
20
|
+
n += 1
|
21
|
+
end
|
22
|
+
|
23
|
+
sleep timeout
|
24
|
+
|
25
|
+
mqtt_client.disconnect
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$:.unshift(File.expand_path("../../../samples", __FILE__))
|
2
|
+
|
3
|
+
require 'aws_iot_device'
|
4
|
+
require 'config_shadow'
|
5
|
+
|
6
|
+
filter_callback = Proc.new do |message|
|
7
|
+
puts "Executing the specific callback for topic: #{message.topic}\n##########################################\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
my_shadow_client = setting_shadow
|
11
|
+
|
12
|
+
my_shadow_client.connect do |client|
|
13
|
+
|
14
|
+
puts "##### Starting test_shadow_client_get ######"
|
15
|
+
client.get_shadow(4, filter_callback)
|
16
|
+
|
17
|
+
puts "##### Starting test_shadow_client_get ######"
|
18
|
+
client.get_shadow(4) do
|
19
|
+
puts "CALLED FROM BLOCK"
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "##### Starting test_shadow_client_get ######"
|
23
|
+
client.get_shadow(4, filter_callback)
|
24
|
+
sleep 5
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.unshift(File.expand_path("../../../samples", __FILE__))
|
2
|
+
|
3
|
+
require 'aws_iot_device'
|
4
|
+
require 'config_shadow'
|
5
|
+
|
6
|
+
filter_callback = Proc.new do |message|
|
7
|
+
puts "Executing the specific callback for topic: #{message.topic}\n##########################################\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
my_shadow_client = setting_shadow
|
11
|
+
|
12
|
+
n = 1
|
13
|
+
3.times do
|
14
|
+
puts "Start shadow_delete\n"
|
15
|
+
my_shadow_client.delete_shadow(5, filter_callback)
|
16
|
+
sleep 0.01
|
17
|
+
json_payload = '{"state":{"desired":{"property":"RubySDK"}}}'
|
18
|
+
my_shadow_client.update_shadow(json_payload, 5)
|
19
|
+
n += 1
|
20
|
+
end
|
21
|
+
|
22
|
+
sleep 2
|
23
|
+
|
24
|
+
my_shadow_client.disconnect
|