spacebunny 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ require 'spacebunny'
2
+ require 'json'
3
+
4
+ # Prerequisites: you have created a device through the SpaceBunny's web interface. You also have a 'data' channel (name
5
+ # is not mandatory, but we'll use this for our example). You have also enabled 'data' channel for the device. See our
6
+ # Getting Started [link] for a quick introduction to SpaceBunny's base concepts.
7
+
8
+ # Once everything is set up get your device's API key from SpaceBunny's web application: on the web interface,
9
+ # go to devices section and create or pick an existing device. Click on the 'SHOW CONFIGURATION' link, copy the API key
10
+ # and substitute it here:
11
+
12
+ device_key = 'your_awesome_device_key'
13
+
14
+ # Let's instantiate a SpaceBunny client, providing the device's API key, that's the fastest and simplest method
15
+ # to create a new client. If, for some reason, you need to customize the settings, take a look at
16
+ # examples/manual_config.rb for an example of connection settings customization.
17
+
18
+ dev = Spacebunny::Device.new key
19
+
20
+ # An equivalent method for providing the API key is through options: Spacebunny::Device.new(key: key)
21
+
22
+ # We need to call 'connect' in order to open the communication with SpaceBunny platform
23
+
24
+ dev.connect
25
+
26
+ # At this point the SDK is auto-configured and ready to use.
27
+ # Configurations are automatically lazy-fetched by the SDK itself when calling 'connect'.
28
+
29
+ # PUBLISHING MESSAGES WITH CONFIRM
30
+
31
+ # As said in the prerequisites, we'll assume that 'data' channel is enabled for your device.
32
+ # If you're in doubt, please check that this is true through SpaceBunny's web interface, by clicking on the device
33
+ # 'edit' (pencil icon) and verifying that 'data' channel is present and enabled for this device. Take a look at Getting
34
+ # Started [link] for a quick introduction to SpaceBunny's base concepts.
35
+
36
+ # Let's publish, for instance, some JSON. Payload can be everything you want, SpaceBunny does not impose any constraint
37
+ # on format or content of payload.
38
+
39
+ # Publish one message every second for a minute.
40
+ count = 0
41
+ 5.times do
42
+ # Generate some random data
43
+ payload = {
44
+ count: count,
45
+ greetings: 'Hello, World!',
46
+ temp: rand(20.0..25.0),
47
+ foo: rand(100..200)
48
+ }.to_json
49
+
50
+ # Hint: the channel name can also be a string e.g: 'data'
51
+ dev.publish :data, payload, with_confirm: true
52
+
53
+ # 'publish' takes two mandatory arguments (channel's name and payload) and a variety of options: one of these options is
54
+ # the 'with_confirm' flag: when set to true this requires SpaceBunny's platform to confirm the receipt of the message.
55
+ # This is useful when message delivery assurance is mandatory for your use case.
56
+ # Take a look at SDK's documentation for further details.
57
+
58
+ # Give some feedback on what has been published
59
+ puts "Published #{payload}"
60
+
61
+ # Take a nap...
62
+ sleep 1
63
+ # Update counter
64
+ count += 1
65
+ end
66
+
67
+ # Wait for publish confirmations: wait for SpaceBunny to confirm that all published messages have been
68
+ # accepted.
69
+ result = dev.wait_for_publish_confirms
70
+
71
+ # 'wait_for_publish_confirms' waits for every message published, regardless the channel, blocking execution until
72
+ # every confirm (or nack) has been received. 'wait_for_publish_confirms' returns an Hash whose keys are the channels'
73
+ # names on which you have published some message.
74
+ # For instance:
75
+
76
+ result.each do |channel, status|
77
+ # If result is false, some message has been nacked. A message may be nacked by SpaceBunny's platform if, for some
78
+ # reason, it cannot take responsibility for the message
79
+ unless status[:all_confirmed]
80
+ # do something with nacked messages
81
+ status[:nacked_set].each do |nacked_message|
82
+ # do something with nacked message
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,109 @@
1
+ require 'spacebunny'
2
+ require 'json'
3
+
4
+ # Prerequisites: you have created a device through the SpaceBunny's web interface. See our Getting Started [link]
5
+ # for a quick introduction to SpaceBunny's base concepts.
6
+
7
+ # Once everything is set up get your device's API key from SpaceBunny's web application: on the web interface,
8
+ # go to devices section and create or pick an existing device. Click on the 'SHOW CONFIGURATION' link, copy the API key
9
+ # and substitute it here:
10
+
11
+ key = 'your_awesome_device_key'
12
+
13
+ # Let's instantiate a SpaceBunny (AMQP by default) client, providing the device's API key, that's the fastest and simplest method
14
+ # to create a new client. If, for some reason, you need to customize the settings, take a look at
15
+ # examples/manual_config.rb for an example of connection settings customization.
16
+
17
+ dev = Spacebunny::Device.new key
18
+
19
+ # An equivalent method for providing the API key is through options: Spacebunny::Device.new(key: key)
20
+
21
+ # We need to call 'connect' in order to open the communication with SpaceBunny platform
22
+
23
+ dev.connect
24
+
25
+ # At this point the SDK is auto-configured and ready to use.
26
+ # Configurations are automatically lazy-fetched by the SDK itself when calling 'connect'.
27
+
28
+
29
+ # RECEIVING MESSAGES
30
+
31
+ puts "Waiting for messages. Publish some from SpaceBunny's web interface to try this out:"
32
+
33
+ # Receiving messages is trivial:
34
+
35
+ dev.inbox(wait: true, ack: :auto) do |message|
36
+ puts "Received: #{message.payload}"
37
+ end
38
+
39
+ # A Ruby block must be supplied to the 'inbox' method. It yields a Device::Message object containing
40
+ # all the useful data:
41
+ #
42
+ # dev.inbox do |message|
43
+ # puts "payload: #{message.payload}"
44
+ #
45
+ # # metadata that can be accessed as an Hash.
46
+ # # metadata[:headers] contains the message's headers for instance
47
+ # puts "metadata: #{message.metadata}"
48
+ #
49
+ # # sender_id (id of the device that sent the message)
50
+ # puts "sender_id: #{message.sender_id}"
51
+ #
52
+ # # channel name i.e. the name of the channel on which the message has been published
53
+ # puts "channel_name: #{message.channel_name}"
54
+ #
55
+ # # Connection and other low level info
56
+ # puts "delivery_info: #{message.delivery_info}"
57
+ # end
58
+
59
+ # inbox method's options:
60
+ # 'wait' (default false) causes the script to wait forever on the receive block. This is useful
61
+ # for 'read-only' scripts like workers or similar.
62
+ # 'ack' option can have two values: :manual (default) or :auto. When :manual you are responsible to ack the messages,
63
+ # for instance:
64
+ #
65
+ # dev.inbox(block: true, ack: :manual) do |message|
66
+ # puts "Received: #{message.payload}"
67
+ # message.ack
68
+ # end
69
+ # When 'ack' is :auto then the SDK will automatically ack the messages when the code provided in the inbox block
70
+ # has terminated execution. This behaviour is useful in cases in which the code executed inside the inbox block
71
+ # will always lead to an acked message (no matter the operations' result).
72
+ # If your code can lead to errors and, for instance, the message must be reprocessed, you must use :manual ack
73
+ # and manually call 'ack' as seen in the example above.
74
+ # Call 'nack' in case the message needs to be reprocessed and it will remain on SpaceBunny until successfully
75
+ # acked. For instance:
76
+
77
+ # dev.inbox(block: true, ack: :manual) do |message|
78
+ # puts message.payload
79
+ #
80
+ # begin
81
+ # # Do something nasty...
82
+ # raise
83
+ #
84
+ # # If everything is ok, ack
85
+ # # (note that this code will never be reached)
86
+ # message.ack
87
+ # rescue Exception => e
88
+ # message.nack
89
+ # end
90
+ # end
91
+
92
+ # ack options are:
93
+ # multiple: false ack multiple messages at once. Ack all the unacked
94
+ # messages up to the current one
95
+
96
+ # nack options are:
97
+ # multiple: false nack multiple messages at once. Nack all the messages up
98
+ # to the current one
99
+ # requeue: false requeue message. If false (default), the message will be
100
+ # discarded by SpaceBunny. If true message will made
101
+ # requeued and made available for delivery again
102
+
103
+
104
+ # Other 'inbox' options:
105
+ # 'discard_from_api' (default false) causes the SDK to filter out messages published through APIs (or WEB UI) or
106
+ # generally sent directly through SpaceBunny's platform.
107
+ # 'discard_mine' (default false) causes the SKD to filter out auto-messages i.e. messages sent from this device
108
+ # and, for some reason, returned to the sender. This can happen in some particular situation such as when using m2m
109
+ # groups.
@@ -0,0 +1,65 @@
1
+ require 'spacebunny'
2
+ require 'json'
3
+
4
+ # Prerequisites: you have created a device through the Space Bunny's web interface. You also have a 'data' channel (name
5
+ # is not mandatory, but we'll use this for our example). You have also enabled 'data' channel for the device. See our
6
+ # Getting Started [link] for a quick introduction to Space Bunny's base concepts.
7
+
8
+ # Once everything is set up get your device's API key from Space Bunny's web application: on the web interface,
9
+ # go to devices section and create or pick an existing device. Click on the 'SHOW CONFIGURATION' link, copy the API key
10
+ # and substitute it here:
11
+
12
+ device_key = 'your_awesome_device_key'
13
+
14
+ # Let's instantiate a Space Bunny client, providing the device's API key, that's the fastest and simplest method
15
+ # to create a new client.
16
+ # Provide `tls: true` to use instantiate a tls-secured connection
17
+
18
+ dev = Spacebunny::Device.new device_key, tls: true
19
+
20
+ # An equivalent method for providing the API key is through options: Spacebunny::Device.new(key: key)
21
+
22
+ # We need to call 'connect' in order to open the communication with Space Bunny platform
23
+
24
+ dev.connect
25
+
26
+ # At this point the SDK is auto-configured and ready to use.
27
+ # Configurations are automatically lazy-fetched by the SDK itself when calling 'connect'.
28
+
29
+ # PUBLISHING MESSAGES
30
+
31
+ # As said in the prerequisites, we'll assume that 'data' channel is enabled for your device.
32
+ # If you're in doubt, please check that this is true through Space Bunny's web interface, by clicking on the device
33
+ # 'edit' (pencil icon) and verifying that 'data' channel is present and enabled for this device. Take a look at Getting
34
+ # Started [link] for a quick introduction to Space Bunny's base concepts.
35
+
36
+ # Let's publish, for instance, some JSON. Payload can be everything you want, Space Bunny does not impose any constraint
37
+ # on format or content of payload.
38
+
39
+ # Publish one message every second for a minute.
40
+ count = 0
41
+ 60.times do
42
+ # Generate some random data
43
+ payload = {
44
+ count: count,
45
+ greetings: "Hello from #{dev.name}!",
46
+ temp: rand(20.0..25.0),
47
+ foo: rand(100..200)
48
+ }.to_json
49
+
50
+ # Hint: the channel name can also be a string e.g: 'data'
51
+ dev.publish :data, payload
52
+
53
+ # 'publish' takes two mandatory arguments (channel's name and payload) and a variety of options: one of these options is
54
+ # the 'with_confirm' flag: when set to true this requires Space Bunny's platform to confirm the receipt of the message.
55
+ # This is useful when message delivery assurance is mandatory for your use case.
56
+ # Take a look at SDK's documentation for further details.
57
+
58
+ # Give some feedback on what has been published
59
+ puts "Published #{payload}"
60
+
61
+ # Take a nap...
62
+ sleep 1
63
+ # Update counter
64
+ count += 1
65
+ end
@@ -0,0 +1,128 @@
1
+ require 'spacebunny'
2
+
3
+ # Prerequisites:
4
+ # - You have created a Live Stream named 'live_data' through the SpaceBunny's web interface.
5
+ # - The Live Stream has, as sources, two devices' 'data' channel.
6
+ # See our Getting Started [link] for a quick introduction to SpaceBunny's base concepts.
7
+
8
+ # Once everything is set up go to Users section on SpaceBunny Web UI, select one user and click on 'Access Keys'
9
+ # Pick or create one access key and copy 'Client' and 'Secret'.
10
+
11
+ client = 'live_stream_key_client'
12
+ secret = 'live_stream_key_secret'
13
+
14
+ # Instantiate a Spacebunny::LiveStream (AMQP by default) client, providing the 'client' and 'secret' options.
15
+
16
+ live = Spacebunny::LiveStream.new client: client, secret: secret
17
+
18
+ # We need to call 'connect' in order to open the communication with SpaceBunny platform
19
+
20
+ live.connect
21
+
22
+ # At this point the SDK is auto-configured and ready to use.
23
+ # Configurations are automatically lazy-fetched by the SDK itself.
24
+
25
+ # RECEIVING MESSAGES
26
+
27
+ # For the sake of this test, we need first to publish some message.
28
+ # If you have configured correctly your Live Stream from Web Interface, it should have at least two devices' data
29
+ # channels as sources: pick one of the devices and copy its Api Key. Fire up another terminal, edit
30
+ # examples/device/auto_config_publish.rb paste the Device Key and run the script.
31
+
32
+ puts 'Waiting for live messages'
33
+
34
+ # Receiving messages is trivial:
35
+
36
+ live.message_from_cache :live_data, wait: true, ack: :auto do |message|
37
+ puts "Received: #{message.payload}"
38
+ end
39
+
40
+ # An alternative to the 'message_from_cache' method is:
41
+ # live.message_from :live_data, from_cache: true, wait: true, ack: :auto do |message|
42
+ # puts "Received #{message.payload}"
43
+ # end
44
+
45
+ # Now fire up another terminal, copy the other device's Api Key and replace it in the auto_config_publish.rb
46
+ # script. If you take a look at the output of running live_stream/receive_messages.rb (this running script)
47
+ # you should see that messages from the devices are almost alternated.
48
+
49
+ # You may notice that we used 'message_from_cache' method: each LiveStream has its own cache that will keep always
50
+ # last 100 messages (FIFO, when there are more than 100 messages, the oldest ones get discarded).
51
+ # If you want to consume messages in a parallel way, you should use the cache and connect as many LiveStream Clients
52
+ # as you need: this way messages will be equally and uniquely distributed to clients.
53
+ # A nacked message will return to the queue and will be delivered to the next available client.
54
+
55
+ # Conversely, if you want that each client will receive a copy of each message, don't use the cache:
56
+
57
+ # live.message_from :live_data, wait: true, ack: :auto do |message|
58
+ # puts "Received a copy of #{message.payload}"
59
+ # end
60
+
61
+ # Every client subscribed to the LiveStream in this way will receive a copy of the message.
62
+
63
+ ### Options and notes
64
+ #
65
+ # A Ruby block must be supplied to the 'message_from' and 'message_from_cache' methods.
66
+ # These methods yield a Device::Message object that wrap all the useful data:
67
+ #
68
+ # dev.message_from do |message|
69
+ # puts "payload: #{message.payload}"
70
+ #
71
+ # # metadata that can be accessed as an Hash.
72
+ # # metadata[:headers] contains the message's headers for instance
73
+ # puts "metadata: #{message.metadata}"
74
+ #
75
+ # # sender_id (id of the device that sent the message)
76
+ # puts "sender_id: #{message.sender_id}"
77
+ #
78
+ # # channel name i.e. the name of the channel on which the message has been published
79
+ # puts "channel_name: #{message.channel_name}"
80
+ #
81
+ # # Connection and other low level info
82
+ # puts "delivery_info: #{message.delivery_info}"
83
+ # end
84
+
85
+ # message_from (message_from_cache) methods' options:
86
+ # 'wait' (default false) causes the script to wait forever on the receive block. This is useful
87
+ # for 'read-only' scripts like workers or similar.
88
+ # 'ack' option can have two values: :manual (default) or :auto. When :manual you are responsible to ack the messages,
89
+ # for instance:
90
+ #
91
+ # dev.message_from(block: true, ack: :manual) do |message|
92
+ # puts "Received: #{message.payload}"
93
+ # message.ack
94
+ # end
95
+ #
96
+ # When 'ack' is :auto then the SDK will automatically ack the messages when the code provided in the message_from block
97
+ # has terminated execution. This behaviour is useful in cases in which the code executed inside the message_from block
98
+ # will always lead to an acked message (no matter the operations' result).
99
+ # If your code can lead to errors and, for instance, the message must be reprocessed, you must use :manual ack
100
+ # and manually call 'ack' as seen in the example above.
101
+ # Call 'nack' in case the message needs to be reprocessed and it will remain on SpaceBunny until successfully
102
+ # acked. For instance:
103
+
104
+ # dev.message_from(block: true, ack: :manual) do |message|
105
+ # puts message.payload
106
+ #
107
+ # begin
108
+ # # Do something nasty...
109
+ # raise
110
+ #
111
+ # # If everything is ok, ack
112
+ # # (note that this code will never be reached)
113
+ # message.ack
114
+ # rescue Exception => e
115
+ # message.nack
116
+ # end
117
+ # end
118
+
119
+ # ack options are:
120
+ # multiple: false ack multiple messages at once. Ack all the unacked
121
+ # messages up to the current one
122
+
123
+ # nack options are:
124
+ # multiple: false nack multiple messages at once. Nack all the messages up
125
+ # to the current one
126
+ # requeue: false requeue message. If false (default), the message will be
127
+ # discarded by SpaceBunny. If true message will made
128
+ # requeued and made available for delivery again
@@ -0,0 +1,22 @@
1
+ require 'spacebunny'
2
+
3
+ # Prerequisites:
4
+ # - You have created a Live Stream named 'live_data' through the SpaceBunny's web interface.
5
+ # - The Live Stream has, as sources, two devices' 'data' channel.
6
+ # See our Getting Started [link] for a quick introduction to SpaceBunny's base concepts.
7
+
8
+ # Once everything is set up go to Users section on SpaceBunny Web UI, select one user and click on 'Access Keys'
9
+ # Pick or create one access key and copy 'Client' and 'Secret'.
10
+
11
+ client = 'live_stream_key_client'
12
+ secret = 'live_stream_key_secret'
13
+
14
+ # Instantiate a Spacebunny::LiveStream (AMQP by default) client, providing the 'client' and 'secret' options.
15
+ # Also provide tls: true to establish a tls-encrypted connection
16
+
17
+ live = Spacebunny::LiveStream.new client: client, secret: secret, tls: true
18
+ live.connect
19
+
20
+ live.message_from_cache :live_data, ack: :auto, wait: true do |message|
21
+ puts "Received: #{message.payload}"
22
+ end
@@ -0,0 +1,23 @@
1
+ # require 'active_support/core_ext/hash/keys'
2
+
3
+ module Spacebunny
4
+ class << self
5
+ attr_accessor :logger
6
+ end
7
+ end
8
+
9
+ require 'spacebunny/version'
10
+ require 'spacebunny/utils'
11
+ require 'spacebunny/logger'
12
+ require 'spacebunny/exceptions'
13
+ require 'spacebunny/endpoint_connection'
14
+ require 'spacebunny/device/base'
15
+ require 'spacebunny/device/message'
16
+ require 'spacebunny/device/amqp'
17
+ require 'spacebunny/live_stream/base'
18
+ require 'spacebunny/live_stream/message'
19
+ require 'spacebunny/live_stream/amqp'
20
+
21
+ def path_to_resources(path)
22
+ File.join(File.dirname(File.expand_path(__FILE__)), path)
23
+ end
@@ -0,0 +1,158 @@
1
+ require 'bunny'
2
+
3
+ module Spacebunny
4
+ module Device
5
+ class Amqp < Base
6
+ DEFAULT_CHANNEL_OPTIONS = { passive: true }
7
+ ACK_TYPES = [:manual, :auto]
8
+
9
+ attr_reader :built_channels, :built_exchanges, :client
10
+
11
+ def initialize(*args)
12
+ super(:amqp, *args)
13
+ @built_channels = {}
14
+ @built_exchanges = {}
15
+ end
16
+
17
+ def connect
18
+ # 'Fix' attributes: start from common connection configs and adjust attributes to match what Bunny
19
+ # wants as connection args
20
+ connection_params = connection_configs.dup
21
+ connection_params[:user] = connection_params.delete :device_id
22
+ connection_params[:password] = connection_params.delete :secret
23
+ connection_params[:port] = connection_params.delete(:tls_port) if connection_params[:tls]
24
+ connection_params[:recover_from_connection_close] = connection_params.delete :auto_recover
25
+ connection_params[:log_level] = connection_params.delete(:log_level) || ::Logger::ERROR
26
+
27
+ # Re-create client every time connect is called
28
+ @client = Bunny.new(connection_params)
29
+ @client.start
30
+ end
31
+
32
+ def channel_from_name(name)
33
+ # In @built_channels in fact we have exchanges
34
+ with_channel_check name do
35
+ @built_exchanges[name]
36
+ end
37
+ end
38
+
39
+ def disconnect
40
+ super
41
+ client.stop
42
+ end
43
+
44
+ def input_channel
45
+ return @input_channel if @input_channel
46
+ @input_channel = client.create_channel
47
+ end
48
+
49
+ def on_receive(options = {})
50
+ unless block_given?
51
+ raise BlockRequired
52
+ end
53
+ blocking = options.fetch :wait, false
54
+ to_ack, auto_ack = parse_ack options.fetch(:ack, :manual)
55
+
56
+ input_queue.subscribe(block: blocking, manual_ack: to_ack) do |delivery_info, metadata, payload|
57
+ message = Device::Message.new self, options, delivery_info, metadata, payload
58
+
59
+ # Skip message if required
60
+ if message.blacklisted?
61
+ message.nack
62
+ next
63
+ end
64
+
65
+ yield message
66
+
67
+ # If ack is :auto then ack current message
68
+ if to_ack && auto_ack
69
+ message.ack
70
+ end
71
+ end
72
+ end
73
+ alias_method :inbox, :on_receive
74
+
75
+ def publish(channel_name, message, options = {})
76
+ check_client
77
+ channel_key = if options[:with_confirm]
78
+ "#{channel_name}_confirm"
79
+ else
80
+ channel_name
81
+ end.to_sym
82
+
83
+ unless @built_exchanges[channel_key]
84
+ @built_exchanges[channel_key] = create_channel(channel_name, options)
85
+ end
86
+ # Call Bunny publish method
87
+ @built_exchanges[channel_key].publish message, channel_options(channel_name, options)
88
+ end
89
+
90
+ def wait_for_publish_confirms
91
+ results = {}
92
+ threads = []
93
+ @built_channels.each do |name, channel|
94
+ if channel.using_publisher_confirmations?
95
+ threads << Thread.new do
96
+ results[name] = { all_confirmed: channel.wait_for_confirms, nacked_set: channel.nacked_set }
97
+ end
98
+ end
99
+ end
100
+ threads.map{ |t| t.join }
101
+ results
102
+ end
103
+
104
+ private
105
+
106
+ # Merge default channel options with provided ones
107
+ def channel_options(channel, options)
108
+ options.merge({routing_key: "#{id}.#{channel}" })
109
+ end
110
+
111
+ # Check if client has been prepared.
112
+ def check_client
113
+ unless client
114
+ raise ClientNotSetup
115
+ end
116
+ unless client.connected?
117
+ if raise_on_error
118
+ raise ClientNotConnected
119
+ else
120
+ @logger.error 'Client not connected! Check internet connection'
121
+ end
122
+ end
123
+ end
124
+
125
+ def create_channel(name, options = {})
126
+ with_channel_check name do
127
+ channel = client.create_channel
128
+ if options.delete(:with_confirm)
129
+ channel.confirm_select
130
+ end
131
+ @built_channels[name] = channel
132
+ channel.direct(id, DEFAULT_CHANNEL_OPTIONS)
133
+ end
134
+ end
135
+
136
+ def input_queue
137
+ return @input_queue if @input_queue
138
+ @input_queue = input_channel.queue "#{id}.inbox", passive: true
139
+ end
140
+
141
+ def parse_ack(ack)
142
+ to_ack = false
143
+ auto_ack = false
144
+ if ack
145
+ raise AckTypeError unless ACK_TYPES.include?(ack)
146
+ to_ack = true
147
+ case ack
148
+ when :manual
149
+ auto_ack = false
150
+ when :auto
151
+ auto_ack = true
152
+ end
153
+ end
154
+ return to_ack, auto_ack
155
+ end
156
+ end
157
+ end
158
+ end