spacebunny 1.0.0

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.
@@ -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