flic 0.0.1 → 0.0.2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/flic.rb +2 -1
  3. data/lib/flic/callbacks.rb +36 -0
  4. data/lib/flic/client.rb +52 -203
  5. data/lib/flic/client/connection_channel.rb +21 -0
  6. data/lib/flic/client/features.rb +15 -0
  7. data/lib/flic/client/features/connection_channel.rb +175 -0
  8. data/lib/flic/client/features/force_disconnect.rb +13 -0
  9. data/lib/flic/client/features/get_button_uuid.rb +43 -0
  10. data/lib/flic/client/features/get_info.rb +52 -0
  11. data/lib/flic/client/features/ping.rb +55 -0
  12. data/lib/flic/client/features/scan.rb +112 -0
  13. data/lib/flic/client/features/scan_wizard.rb +131 -0
  14. data/lib/flic/client/scan_wizard.rb +14 -0
  15. data/lib/flic/client/scanner.rb +12 -0
  16. data/lib/flic/client/server_info.rb +16 -0
  17. data/lib/flic/protocol.rb +1 -0
  18. data/lib/flic/protocol/commands/change_mode_parameters.rb +3 -2
  19. data/lib/flic/protocol/commands/create_connection_channel.rb +3 -2
  20. data/lib/flic/protocol/commands/remove_connection_channel.rb +1 -1
  21. data/lib/flic/{client → protocol}/connection.rb +20 -26
  22. data/lib/flic/protocol/events/button_click_or_hold.rb +1 -1
  23. data/lib/flic/protocol/events/button_single_or_double_click.rb +1 -1
  24. data/lib/flic/protocol/events/button_single_or_double_click_or_hold.rb +1 -1
  25. data/lib/flic/protocol/events/button_up_or_down.rb +1 -1
  26. data/lib/flic/protocol/events/connection_channel_removed.rb +2 -2
  27. data/lib/flic/protocol/events/connection_status_changed.rb +1 -1
  28. data/lib/flic/protocol/events/create_connection_channel_response.rb +1 -1
  29. data/lib/flic/protocol/events/get_info_response.rb +3 -2
  30. data/lib/flic/protocol/primitives.rb +1 -0
  31. data/lib/flic/protocol/primitives/disconnect_time.rb +29 -0
  32. data/lib/flic/simple_client.rb +116 -0
  33. data/lib/flic/version.rb +1 -1
  34. metadata +18 -6
  35. data/lib/flic/event_bus.rb +0 -81
  36. data/lib/flic/event_bus/driver.rb +0 -23
  37. data/lib/flic/event_bus/subscription.rb +0 -66
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ade3fb4aee08cb658059f896f2ba8e3524b7ee76
4
- data.tar.gz: b04d34afa0853fb278f57a58c4569d02d53c02dc
3
+ metadata.gz: 5c7e0c99f004d000f1ed0b0296403fbc8e723a4a
4
+ data.tar.gz: d48a0bb111640992b77d6bac24f3cdd2e70b2b79
5
5
  SHA512:
6
- metadata.gz: 625cdb437414faa416799c2a64b581880aa7847fd4f774cc34e16f3c74f882928fbd7dfcc8f1c8b88f73c6c1bc79becb9a0f5e9f8a7e3e54fa826707bbc23835
7
- data.tar.gz: d0e5ec61e2394840c33232e60cc2db1840df09587198f99b443d28786e22078ae345fae7f66dc7939cd0ceeb0140297dc3db8729849b27acf0fa253375aada44
6
+ metadata.gz: fccb7b55b186dbae110a268d78682ae2cc1213b7fc3f0883f08c1fc330549c44b61e868a4a17c8b859e11688cefcc1fa3834cd34932316da3ac5def7135f7796
7
+ data.tar.gz: 2181c732b2ad6340fc12bd4865de9d2da1dea2e2118b4c09dc2ee9fa5a1d221ee88299609ab2495cb20c665e19d8d3b5618831f3fcaf71be95554ee240bb04fd
@@ -1,7 +1,8 @@
1
1
  require 'flic/version'
2
2
 
3
3
  module Flic
4
+ autoload :Callbacks, 'flic/callbacks'
4
5
  autoload :Client, 'flic/client'
5
- autoload :EventBus, 'flic/event_bus'
6
6
  autoload :Protocol, 'flic/protocol'
7
+ autoload :SimpleClient, 'flic/simple_client'
7
8
  end
@@ -0,0 +1,36 @@
1
+ require 'flic'
2
+
3
+ require 'thread'
4
+
5
+ module Flic
6
+ module Callbacks
7
+ SEMAPHORE = Mutex.new
8
+
9
+ private
10
+
11
+ def define_callbacks(*callback_names)
12
+ callback_names.each do |callback_name|
13
+ semaphore_instance_variable = :"@#{callback_name}_callbacks_semaphore"
14
+ callbacks_instance_variable = :"@#{callback_name}_callbacks"
15
+
16
+ define_method :"#{callback_name}" do |*args, &callback|
17
+ semaphore = SEMAPHORE.synchronize do
18
+ instance_variable_get(semaphore_instance_variable) ||
19
+ instance_variable_set(semaphore_instance_variable, Mutex.new)
20
+ end
21
+
22
+ semaphore.synchronize do
23
+ callbacks = instance_variable_get(callbacks_instance_variable) ||
24
+ instance_variable_set(callbacks_instance_variable, [])
25
+
26
+ if callback
27
+ callbacks << callback
28
+ else
29
+ callbacks.each { |callback| callback.call *args }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,14 +1,29 @@
1
1
  require 'flic'
2
- require 'flic/event_bus'
3
- require 'flic/protocol'
2
+
3
+ require 'thread'
4
+ require 'socket'
4
5
 
5
6
  module Flic
6
7
  class Client
7
- autoload :Connection, 'flic/client/connection'
8
-
9
8
  class Error < StandardError; end
10
9
  class ClientShutdownError < Error; end
11
10
 
11
+ autoload :ConnectionChannel, 'flic/client/connection_channel'
12
+ autoload :Features, 'flic/client/features'
13
+ autoload :ScanWizard, 'flic/client/scan_wizard'
14
+ autoload :Scanner, 'flic/client/scanner'
15
+ autoload :ServerInfo, 'flic/client/server_info'
16
+
17
+ extend Callbacks
18
+
19
+ prepend Features::ConnectionChannel
20
+ prepend Features::ForceDisconnect
21
+ prepend Features::GetButtonUuid
22
+ prepend Features::GetInfo
23
+ prepend Features::Ping
24
+ prepend Features::Scan
25
+ prepend Features::ScanWizard
26
+
12
27
  class << self
13
28
  def open(*args)
14
29
  client = new(*args)
@@ -21,227 +36,61 @@ module Flic
21
36
  end
22
37
  end
23
38
 
24
- attr_reader :connection, :driver
39
+ attr_reader :host, :port, :socket, :connection
25
40
 
26
- def initialize(*connection_args)
27
- @connection = Connection.new(*connection_args)
41
+ define_callbacks :new_button_verified, :bluetooth_controller_state_changed,
42
+ :connections_exhausted, :connection_available
28
43
 
29
- @driver = EventBus::Driver.new do |event_bus|
30
- begin
31
- connection.listen do |event|
32
- event_bus.broadcast(event)
33
- end
34
- rescue Connection::ConnectionClosedError
35
- nil
36
- rescue Protocol::Error => protocol_error
37
- warn protocol_error
38
-
39
- retry
40
- end
41
- end
42
44
 
45
+ def initialize(host = 'localhost', port = 5551)
46
+ @host, @port = host, port
47
+ @handle_next_event_semaphore = Mutex.new
48
+ @socket = TCPSocket.new(host, port)
49
+ @connection = Protocol::Connection.new(socket)
43
50
  yield self if block_given?
44
51
  end
45
52
 
46
- def hostname
47
- connection.hostname
48
- end
49
-
50
- def port
51
- connection.port
52
- end
53
-
54
- def shutdown?
55
- connection.closed?
56
- end
57
-
58
53
  def shutdown
59
54
  connection.close
60
55
  end
61
56
 
62
- def ping ping_id = rand(2**32)
63
- request Protocol::Commands::Ping.new(ping_id: ping_id) do |event|
64
- Protocol::Events::PingResponse === event &&
65
- event.ping_id == ping_id
66
- end
67
-
68
- true
69
- end
70
-
71
- def server_info
72
- request Protocol::Commands::GetInfo, Protocol::Events::GetInfoResponse
73
- end
74
-
75
- def button_uuid(bluetooth_address)
76
- command = Protocol::Commands::GetButtonUuid.new(bluetooth_address: bluetooth_address)
77
-
78
- response = request command do |event|
79
- Protocol::Events::GetButtonUuidResponse === event &&
80
- event.bluetooth_address == command.bluetooth_address
81
- end
82
-
83
- unless response.uuid == Protocol::INVALID_BUTTON_UUID
84
- response.uuid
85
- end
86
- end
87
-
88
- def buttons
89
- server_info.verified_buttons
90
- end
91
-
92
- def disconnect_button(bluetooth_address)
93
- send_command Protocol::Commands::ForceDisconnect.new(bluetooth_address: bluetooth_address)
94
- end
95
-
96
- def connect_button
97
- bluetooth_address = nil
98
-
99
- result = scan_wizard do |button_type, _bluetooth_address|
100
- bluetooth_address = _bluetooth_address if button_type == :public
101
- end
102
-
103
- bluetooth_address if result == :success
104
- end
105
-
106
- def scan(scan_id = rand(2**32))
107
- subscribe do |subscription|
108
- send_command Protocol::Commands::CreateScanner.new(scan_id: scan_id)
109
-
110
- begin
111
- subscription.listen do |event|
112
- if Protocol::Events::AdvertisementPacket === event && event.scan_id == scan_id
113
- yield event.bluetooth_address, event.name, event.rssi, event.is_private, event.is_already_verified
114
- end
115
- end
116
- ensure
117
- send_command Protocol::Commands::RemoveScanner.new(scan_id: scan_id)
118
- end
119
- end
120
- end
121
-
122
- def scan_wizard(scan_wizard_id = rand(2**32))
123
- subscribe do |subscription|
124
- send_command Protocol::Commands::CreateScanWizard.new(scan_wizard_id: scan_wizard_id)
125
-
57
+ def handle_next_event
58
+ @handle_next_event_semaphore.synchronize do
126
59
  begin
127
- bluetooth_address = nil
128
- name = nil
129
-
130
- result = subscription.listen do |event|
131
- case event
132
- when Protocol::Events::ScanWizardFoundPrivateButton
133
- yield :private, nil, nil
134
- when Protocol::Events::ScanWizardFoundPublicButton
135
- bluetooth_address, name = event.bluetooth_address, event.name
136
- when Protocol::Events::ScanWizardButtonConnected
137
- yield :public, bluetooth_address, name
138
- when Protocol::Events::ScanWizardCompleted
139
- break event.scan_wizard_result
140
- end
141
- end
142
- ensure
143
- send_command Protocol::Commands::CancelScanWizard.new(scan_wizard_id: scan_wizard_id) unless result
144
- end
145
- end
146
- end
147
-
148
- def channel(bluetooth_address, latency_mode = :normal, auto_disconnect_time = nil, connection_id = rand(2**32))
149
- auto_disconnect_time = 512 unless auto_disconnect_time # 512 means disabled
150
-
151
- subscribe do |subscription|
152
- send_command Protocol::Commands::CreateConnectionChannel.new(
153
- connection_id: connection_id,
154
- bluetooth_address: bluetooth_address,
155
- latency_mode: latency_mode,
156
- auto_disconnect_time: auto_disconnect_time
157
- )
60
+ handle_event connection.recv_event
61
+ rescue Protocol::Connection::ConnectionClosedError
62
+ shutdown
158
63
 
159
- is_removed = false
160
-
161
- begin
162
- subscription.listen do |event|
163
- case event
164
- when Protocol::Events::ButtonSingleOrDoubleClickOrHold
165
- yield bluetooth_address, event.click_type, event.time_difference
166
- when Protocol::Events::ConnectionChannelRemoved
167
- is_removed = true
168
- break
169
- end
170
- end
171
- ensure
172
- send_command Protocol::Commands::RemoveConnectionChannel.new(connection_id: connection_id) unless is_removed
64
+ raise ClientShutdownError, 'The connection has been closed'
173
65
  end
174
66
  end
175
67
  end
176
68
 
177
- def listen(*bluetooth_addresses)
178
- subscribe do |subscription|
179
- connection_id_bluetooth_address = {}
180
-
181
- begin
182
- bluetooth_addresses.each do |bluetooth_address|
183
- connection_id = rand(2**32)
184
-
185
- send_command Protocol::Commands::CreateConnectionChannel.new(
186
- connection_id: connection_id,
187
- bluetooth_address: bluetooth_address,
188
- latency_mode: :normal,
189
- auto_disconnect_time: 512
190
- )
191
-
192
- connection_id_bluetooth_address[connection_id] = bluetooth_address
193
- end
194
-
195
- subscription.listen do |event|
196
- case event
197
- when Protocol::Events::ButtonSingleOrDoubleClickOrHold
198
- bluetooth_address = connection_id_bluetooth_address[event.connection_id]
199
- yield bluetooth_address, event.click_type, event.time_difference
200
- when Protocol::Events::ConnectionChannelRemoved
201
- bluetooth_address = connection_id_bluetooth_address[event.connection_id]
202
- connection_id_bluetooth_address.delete event.connection_id
203
- raise "connection to #{bluetooth_address} was removed"
204
- end
205
- end
206
- ensure
207
- connection_id_bluetooth_address.each do |connection_id, _|
208
- send_command Protocol::Commands::RemoveConnectionChannel.new(
209
- connection_id: connection_id
210
- )
211
- end
212
- end
213
- end
69
+ def enter_main_loop
70
+ loop { handle_next_event }
214
71
  end
215
72
 
216
73
  private
217
74
 
218
- def event_bus
219
- @event_bus ||= driver.event_bus
220
- end
221
-
222
75
  def send_command(command)
223
- command = command.new if Class === command
224
76
  connection.send_command(command)
225
- rescue Client::Connection::ConnectionClosedError
226
- raise ClientShutdownError
227
- end
228
-
229
- def subscribe
230
- event_bus.subscribe { |subscription| yield subscription }
231
- rescue EventBus::EventBusShutdown
232
- raise ClientShutdownError
233
- end
234
-
235
- def request(command, response_matcher = Proc.new)
236
- subscribe do |subscription|
237
- send_command command
238
-
239
- subscription.listen do |event|
240
- if response_matcher === event
241
- break event
242
- end
243
- end
77
+ rescue Protocol::Connection::ConnectionClosedError
78
+ shutdown
79
+
80
+ raise ClientShutdownError, 'The connection has been closed'
81
+ end
82
+
83
+ def handle_event(event)
84
+ case event
85
+ when Protocol::Events::NewVerifiedButton
86
+ new_button_verified event.bluetooth_address
87
+ when Protocol::Events::BluetoothControllerStateChange
88
+ bluetooth_controller_state_changed event.bluetooth_controller_state
89
+ when Protocol::Events::NoSpaceForNewConnection
90
+ connections_exhausted
91
+ when Protocol::Events::GotSpaceForNewConnection
92
+ connection_available
244
93
  end
245
94
  end
246
95
  end
247
- end
96
+ end
@@ -0,0 +1,21 @@
1
+ require 'flic/client'
2
+
3
+ module Flic
4
+ class Client
5
+ class ConnectionChannel
6
+ extend Callbacks
7
+
8
+ attr_reader :button_bluetooth_address, :latency_mode, :auto_disconnect_time
9
+ attr_accessor :status
10
+
11
+ define_callbacks :added, :removed, :failed_to_add, :status_changed,
12
+ :button_up_or_down, :button_click_or_hold,
13
+ :button_single_click_or_double_click, :button_single_click_or_double_click_or_hold
14
+
15
+ def initialize(button_bluetooth_address, latency_mode, auto_disconnect_time = nil)
16
+ @button_bluetooth_address, @latency_mode, @auto_disconnect_time = button_bluetooth_address, latency_mode, auto_disconnect_time
17
+ @status = :disconnected
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ require 'flic/client'
2
+
3
+ module Flic
4
+ class Client
5
+ module Features
6
+ autoload :ConnectionChannel, 'flic/client/features/connection_channel'
7
+ autoload :ForceDisconnect, 'flic/client/features/force_disconnect'
8
+ autoload :GetButtonUuid, 'flic/client/features/get_button_uuid'
9
+ autoload :GetInfo, 'flic/client/features/get_info'
10
+ autoload :Ping, 'flic/client/features/ping'
11
+ autoload :Scan, 'flic/client/features/scan'
12
+ autoload :ScanWizard, 'flic/client/features/scan_wizard'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,175 @@
1
+ require 'flic/client/features'
2
+
3
+ require 'thread'
4
+
5
+ module Flic
6
+ class Client
7
+ module Features
8
+ module ConnectionChannel
9
+ def initialize(*)
10
+ @connection_channel_id_connection_channel_semaphore = Mutex.new
11
+ @connection_channel_id_connection_channel = {}
12
+
13
+ super
14
+ end
15
+
16
+ def connection_channels
17
+ @connection_channel_id_connection_channel_semaphore.synchronize { @connection_channel_id_connection_channel.values }
18
+ end
19
+
20
+ def add_connection_channel(connection_channel)
21
+ connection_channel_id = _add_connection_channel(connection_channel)
22
+
23
+ if connection_channel_id
24
+ send_command Protocol::Commands::CreateConnectionChannel.new(
25
+ connection_channel_id: connection_channel_id,
26
+ bluetooth_address: connection_channel.button_bluetooth_address,
27
+ latency_mode: connection_channel.latency_mode,
28
+ auto_disconnect_time: connection_channel.auto_disconnect_time
29
+ )
30
+ end
31
+ end
32
+
33
+ def remove_connection_channel(connection_channel)
34
+ connection_channel_id = find_connection_channel_id_by_connection_channel(connection_channel)
35
+
36
+ if connection_channel_id
37
+ send_command Protocol::Commands::RemoveConnectionChannel.new(
38
+ connection_channel_id: connection_channel_id
39
+ )
40
+ end
41
+ end
42
+
43
+ def shutdown(*)
44
+ connection_channels.each do |connection_channel|
45
+ _remove_connection_channel(connection_channel)
46
+ connection_channel.removed self
47
+ end
48
+
49
+ super
50
+ end
51
+
52
+ private
53
+
54
+ def handle_event(event)
55
+ case event
56
+ when Protocol::Events::CreateConnectionChannelResponse
57
+ connection_channel = find_connection_channel_by_connection_channel_id(event.connection_channel_id)
58
+
59
+ if connection_channel
60
+ if event.error == :no_error
61
+ connection_channel.added self
62
+ else
63
+ _remove_connection_channel(connection_channel)
64
+ connection_channel.failed_to_add self, event.error
65
+ end
66
+ end
67
+
68
+ when Protocol::Events::ConnectionStatusChanged
69
+ connection_channel = find_connection_channel_by_connection_channel_id(event.connection_channel_id)
70
+
71
+ if connection_channel
72
+ connection_channel.status = event.connection_status
73
+ connection_channel.status_changed event.connection_status
74
+ end
75
+
76
+ when Protocol::Events::ButtonUpOrDown
77
+ connection_channel = find_connection_channel_by_connection_channel_id(event.connection_channel_id)
78
+
79
+ if connection_channel
80
+ connection_channel.button_up_or_down event.click_type, event.time_difference, event.was_queued
81
+ end
82
+ when Protocol::Events::ButtonClickOrHold
83
+ connection_channel = find_connection_channel_by_connection_channel_id(event.connection_channel_id)
84
+
85
+ if connection_channel
86
+ connection_channel.button_click_or_hold event.click_type, event.time_difference, event.was_queued
87
+ end
88
+
89
+ when Protocol::Events::ButtonSingleOrDoubleClick
90
+ connection_channel = find_connection_channel_by_connection_channel_id(event.connection_channel_id)
91
+
92
+ if connection_channel
93
+ connection_channel.button_single_click_or_double_click event.click_type, event.time_difference, event.was_queued
94
+ end
95
+
96
+ when Protocol::Events::ButtonSingleOrDoubleClickOrHold
97
+ connection_channel = find_connection_channel_by_connection_channel_id(event.connection_channel_id)
98
+
99
+ if connection_channel
100
+ connection_channel.button_single_click_or_double_click_or_hold event.click_type, event.time_difference, event.was_queued
101
+ end
102
+
103
+ when Protocol::Events::ConnectionChannelRemoved
104
+ connection_channel = find_connection_channel_by_connection_channel_id(event.connection_channel_id)
105
+
106
+ if connection_channel
107
+ _remove_connection_channel(connection_channel)
108
+ connection_channel.removed self, event.reason
109
+ end
110
+
111
+ else
112
+ super
113
+ end
114
+ end
115
+
116
+ def find_connection_channel_by_connection_channel_id(needle)
117
+ @connection_channel_id_connection_channel_semaphore.synchronize do
118
+ @connection_channel_id_connection_channel.each do |connection_channel_id, connection_channel|
119
+ return connection_channel if connection_channel_id == needle
120
+ end
121
+ end
122
+
123
+ nil
124
+ end
125
+
126
+ def find_connection_channel_id_by_connection_channel(needle)
127
+ @connection_channel_id_connection_channel_semaphore.synchronize do
128
+ @connection_channel_id_connection_channel.each do |connection_channel_id, connection_channel|
129
+ return connection_channel_id if connection_channel == needle
130
+ end
131
+ end
132
+
133
+ nil
134
+ end
135
+
136
+ def _add_connection_channel(connection_channel)
137
+ connection_channel_id = nil
138
+
139
+ @connection_channel_id_connection_channel_semaphore.synchronize do
140
+ unless @connection_channel_id_connection_channel.values.include?(connection_channel)
141
+ loop do
142
+ connection_channel_id = rand(2**32)
143
+
144
+ break unless @connection_channel_id_connection_channel.has_key?(connection_channel_id)
145
+ end
146
+
147
+ @connection_channel_id_connection_channel[connection_channel_id] = connection_channel
148
+ end
149
+ end
150
+
151
+ connection_channel_id
152
+ end
153
+
154
+ def _remove_connection_channel(connection_channel)
155
+ connection_channel_id = nil
156
+
157
+ @connection_channel_id_connection_channel_semaphore.synchronize do
158
+ if @connection_channel_id_connection_channel.values.include?(connection_channel)
159
+ @connection_channel_id_connection_channel.each do |_connection_channel_id, _connection_channel|
160
+ if connection_channel == _connection_channel
161
+ connection_channel_id = _connection_channel_id
162
+ break
163
+ end
164
+ end
165
+
166
+ @connection_channel_id_connection_channel.delete connection_channel_id if connection_channel_id
167
+ end
168
+ end
169
+
170
+ connection_channel_id
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end