flic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +30 -0
  8. data/Rakefile +12 -0
  9. data/flic.gemspec +25 -0
  10. data/lib/flic.rb +7 -0
  11. data/lib/flic/client.rb +247 -0
  12. data/lib/flic/client/connection.rb +87 -0
  13. data/lib/flic/event_bus.rb +81 -0
  14. data/lib/flic/event_bus/driver.rb +23 -0
  15. data/lib/flic/event_bus/subscription.rb +66 -0
  16. data/lib/flic/protocol.rb +64 -0
  17. data/lib/flic/protocol/commands.rb +44 -0
  18. data/lib/flic/protocol/commands/cancel_scan_wizard.rb +15 -0
  19. data/lib/flic/protocol/commands/change_mode_parameters.rb +17 -0
  20. data/lib/flic/protocol/commands/command.rb +21 -0
  21. data/lib/flic/protocol/commands/create_connection_channel.rb +20 -0
  22. data/lib/flic/protocol/commands/create_scan_wizard.rb +15 -0
  23. data/lib/flic/protocol/commands/create_scanner.rb +14 -0
  24. data/lib/flic/protocol/commands/force_disconnect.rb +15 -0
  25. data/lib/flic/protocol/commands/get_button_uuid.rb +15 -0
  26. data/lib/flic/protocol/commands/get_info.rb +12 -0
  27. data/lib/flic/protocol/commands/ping.rb +14 -0
  28. data/lib/flic/protocol/commands/remove_connection_channel.rb +14 -0
  29. data/lib/flic/protocol/commands/remove_scanner.rb +14 -0
  30. data/lib/flic/protocol/events.rb +60 -0
  31. data/lib/flic/protocol/events/advertisement_packet.rb +25 -0
  32. data/lib/flic/protocol/events/bluetooth_controller_state_change.rb +15 -0
  33. data/lib/flic/protocol/events/button_click_or_hold.rb +19 -0
  34. data/lib/flic/protocol/events/button_single_or_double_click.rb +19 -0
  35. data/lib/flic/protocol/events/button_single_or_double_click_or_hold.rb +19 -0
  36. data/lib/flic/protocol/events/button_up_or_down.rb +19 -0
  37. data/lib/flic/protocol/events/connection_channel_removed.rb +16 -0
  38. data/lib/flic/protocol/events/connection_status_changed.rb +19 -0
  39. data/lib/flic/protocol/events/create_connection_channel_response.rb +18 -0
  40. data/lib/flic/protocol/events/event.rb +21 -0
  41. data/lib/flic/protocol/events/get_button_uuid_response.rb +17 -0
  42. data/lib/flic/protocol/events/get_info_response.rb +27 -0
  43. data/lib/flic/protocol/events/got_space_for_new_connection.rb +14 -0
  44. data/lib/flic/protocol/events/new_verified_button.rb +15 -0
  45. data/lib/flic/protocol/events/no_space_for_new_connection.rb +14 -0
  46. data/lib/flic/protocol/events/ping_response.rb +14 -0
  47. data/lib/flic/protocol/events/scan_wizard_button_connected.rb +14 -0
  48. data/lib/flic/protocol/events/scan_wizard_completed.rb +16 -0
  49. data/lib/flic/protocol/events/scan_wizard_found_private_button.rb +14 -0
  50. data/lib/flic/protocol/events/scan_wizard_found_public_button.rb +19 -0
  51. data/lib/flic/protocol/packet_header.rb +12 -0
  52. data/lib/flic/protocol/primitives.rb +22 -0
  53. data/lib/flic/protocol/primitives/bluetooth_address.rb +35 -0
  54. data/lib/flic/protocol/primitives/bluetooth_address_type.rb +13 -0
  55. data/lib/flic/protocol/primitives/bluetooth_controller_state.rb +14 -0
  56. data/lib/flic/protocol/primitives/boolean.rb +19 -0
  57. data/lib/flic/protocol/primitives/click_type.rb +17 -0
  58. data/lib/flic/protocol/primitives/connection_status.rb +14 -0
  59. data/lib/flic/protocol/primitives/create_connection_channel_error.rb +13 -0
  60. data/lib/flic/protocol/primitives/device_name.rb +44 -0
  61. data/lib/flic/protocol/primitives/disconnect_reason.rb +15 -0
  62. data/lib/flic/protocol/primitives/enum.rb +85 -0
  63. data/lib/flic/protocol/primitives/latency_mode.rb +14 -0
  64. data/lib/flic/protocol/primitives/removed_reason.rb +18 -0
  65. data/lib/flic/protocol/primitives/scan_wizard_result.rb +18 -0
  66. data/lib/flic/protocol/primitives/uuid.rb +23 -0
  67. data/lib/flic/version.rb +3 -0
  68. metadata +180 -0
@@ -0,0 +1,81 @@
1
+ require 'flic'
2
+
3
+ require 'thread'
4
+
5
+ module Flic
6
+ class EventBus
7
+ class Error < StandardError; end
8
+ class EventBusShutdown < Error; end
9
+
10
+ autoload :Driver, 'flic/event_bus/driver'
11
+ autoload :Subscription, 'flic/event_bus/subscription'
12
+
13
+ def initialize
14
+ @semaphore = Mutex.new
15
+ @subscriptions = []
16
+ @is_shutdown = false
17
+ end
18
+
19
+ def subscribe(subscription = Subscription.new)
20
+ @semaphore.synchronize do
21
+ raise EventBusShutdown if shutdown?
22
+
23
+ @subscriptions << subscription
24
+ end
25
+
26
+ if block_given?
27
+ begin
28
+ yield subscription
29
+ ensure
30
+ unsubscribe subscription
31
+ end
32
+ end
33
+ end
34
+
35
+ def unsubscribe(subscription)
36
+ @semaphore.synchronize do
37
+ raise EventBusShutdown if shutdown?
38
+
39
+ @subscriptions.delete subscription
40
+ end
41
+
42
+ subscription.destroy
43
+ end
44
+
45
+ def listen
46
+ subscribe do |subscription|
47
+ subscription.listen do
48
+ yield
49
+ end
50
+ end
51
+ end
52
+
53
+ def broadcast(*args)
54
+ @semaphore.synchronize do
55
+ raise EventBusShutdown if shutdown?
56
+
57
+ @subscriptions.each do |subscription|
58
+ subscription.publish(*args)
59
+ end
60
+ end
61
+ end
62
+
63
+ def shutdown?
64
+ @is_shutdown
65
+ end
66
+
67
+ def shutdown
68
+ @semaphore.synchronize do
69
+ unless shutdown?
70
+ @is_shutdown = true
71
+
72
+ @subscriptions.each do |subscription|
73
+ subscription.destroy
74
+ end
75
+
76
+ @subscriptions.clear
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,23 @@
1
+ require 'flic/event_bus'
2
+
3
+ require 'thread'
4
+
5
+ module Flic
6
+ class EventBus
7
+ class Driver < Thread
8
+ attr_reader :event_bus
9
+
10
+ def initialize
11
+ @event_bus = EventBus.new
12
+
13
+ super() do
14
+ begin
15
+ yield(event_bus)
16
+ ensure
17
+ event_bus.shutdown
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,66 @@
1
+ require 'flic/event_bus'
2
+
3
+ require 'thread'
4
+
5
+ module Flic
6
+ class EventBus
7
+ class Subscription
8
+ class SubscriptionDestroyed < Error
9
+ end
10
+
11
+ def initialize
12
+ @queue = Queue.new
13
+ @semaphore = Mutex.new
14
+ @is_destroyed = false
15
+ end
16
+
17
+ def listen
18
+ loop do
19
+ yield *next_event!
20
+ end
21
+ end
22
+
23
+ def next_event_nonblock
24
+ next_event unless @queue.empty?
25
+ end
26
+
27
+ def next_event
28
+ @semaphore.synchronize do
29
+ unless destroyed?
30
+ control, event = @queue.pop
31
+
32
+ case control
33
+ when :event
34
+ event
35
+ when :destroy
36
+ @is_destroyed = true
37
+
38
+ nil
39
+ else
40
+ raise NotImplementedError
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def next_event!
47
+ event = next_event
48
+ raise SubscriptionDestroyed if destroyed?
49
+ event
50
+ end
51
+
52
+ def publish(*args)
53
+ raise SubscriptionDestroyed if destroyed?
54
+ @queue.push [:event, *args]
55
+ end
56
+
57
+ def destroyed?
58
+ @is_destroyed
59
+ end
60
+
61
+ def destroy
62
+ @queue.push [:destroy] unless destroyed?
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,64 @@
1
+ require 'flic'
2
+
3
+ module Flic
4
+ module Protocol
5
+ class Error < StandardError; end
6
+
7
+ autoload :Commands, 'flic/protocol/commands'
8
+ autoload :Events, 'flic/protocol/events'
9
+ autoload :PacketHeader, 'flic/protocol/packet_header'
10
+ autoload :Primitives, 'flic/protocol/primitives'
11
+
12
+ INVALID_BUTTON_UUID = '00000000-0000-0000-0000-000000000000'.freeze
13
+
14
+ def self.serialize_command(command)
15
+ case command
16
+ when Commands::Command
17
+ command.to_binary_s
18
+ else
19
+ raise NotImplementedError
20
+ end
21
+ rescue
22
+ raise Error, "Cannot serialize command `#{command.inspect}`"
23
+ end
24
+
25
+ def self.parse_command(serialized_command)
26
+ command = Commands::Command.read(serialized_command)
27
+ opcode = command.opcode
28
+ command_class = Commands::Command.command_class_for_opcode(opcode)
29
+
30
+ if command_class
31
+ command_class.read(serialized_command)
32
+ else
33
+ raise NotImplementedError, "Unknown command opcode #{opcode}"
34
+ end
35
+ rescue
36
+ raise Error, "Cannot parse event `#{serialized_command.inspect}`"
37
+ end
38
+
39
+ def self.serialize_event(event)
40
+ case event
41
+ when Commands::Event
42
+ event.to_binary_s
43
+ else
44
+ raise NotImplementedError
45
+ end
46
+ rescue
47
+ raise Error, "Cannot serialize event `#{event.inspect}`"
48
+ end
49
+
50
+ def self.parse_event(serialized_event)
51
+ event = Events::Event.read(serialized_event)
52
+ opcode = event.opcode
53
+ event_class = Events.event_class_for_opcode(opcode)
54
+
55
+ if event_class
56
+ event_class.read(serialized_event)
57
+ else
58
+ raise NotImplementedError, "Unknown event opcode #{opcode}"
59
+ end
60
+ rescue
61
+ raise Error, "Cannot parse event `#{serialized_event.inspect}`"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ require 'flic/protocol'
2
+
3
+ module Flic
4
+ module Protocol
5
+ module Commands
6
+ autoload :CancelScanWizard, 'flic/protocol/commands/cancel_scan_wizard'
7
+ autoload :ChangeModeParameters, 'flic/protocol/commands/change_mode_parameters'
8
+ autoload :Command, 'flic/protocol/commands/command'
9
+ autoload :CreateConnectionChannel, 'flic/protocol/commands/create_connection_channel'
10
+ autoload :CreateScanWizard, 'flic/protocol/commands/create_scan_wizard'
11
+ autoload :CreateScanner, 'flic/protocol/commands/create_scanner'
12
+ autoload :ForceDisconnect, 'flic/protocol/commands/force_disconnect'
13
+ autoload :GetButtonUuid, 'flic/protocol/commands/get_button_uuid'
14
+ autoload :GetInfo, 'flic/protocol/commands/get_info'
15
+ autoload :Ping, 'flic/protocol/commands/ping'
16
+ autoload :RemoveConnectionChannel, 'flic/protocol/commands/remove_connection_channel'
17
+ autoload :RemoveScanner, 'flic/protocol/commands/remove_scanner'
18
+
19
+ COMMAND_CLASS_OPCODE = {
20
+ Commands::GetInfo => 0x00,
21
+ Commands::CreateScanner => 0x01,
22
+ Commands::RemoveScanner => 0x02,
23
+ Commands::CreateConnectionChannel => 0x03,
24
+ Commands::RemoveConnectionChannel => 0x04,
25
+ Commands::ForceDisconnect => 0x05,
26
+ Commands::ChangeModeParameters => 0x06,
27
+ Commands::Ping => 0x07,
28
+ Commands::GetButtonUuid => 0x08,
29
+ Commands::CreateScanWizard => 0x09,
30
+ Commands::CancelScanWizard => 0x0A
31
+ }.freeze
32
+
33
+ OPCODE_COMMAND_CLASS = COMMAND_CLASS_OPCODE.invert.freeze
34
+
35
+ def self.command_class_for_opcode(opcode)
36
+ OPCODE_COMMAND_CLASS[opcode]
37
+ end
38
+
39
+ def self.opcode_for_command_class(command_class)
40
+ COMMAND_CLASS_OPCODE[command_class]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ require 'flic/protocol/commands'
2
+ require 'flic/protocol/commands/command'
3
+ require 'flic/protocol/primitives/bluetooth_address'
4
+
5
+ module Flic
6
+ module Protocol
7
+ module Commands
8
+ class CancelScanWizard < Command
9
+ endian :little
10
+
11
+ uint32 :scan_wizard_id
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ require 'flic/protocol/commands'
2
+ require 'flic/protocol/commands/command'
3
+ require 'flic/protocol/primitives/latency_mode'
4
+
5
+ module Flic
6
+ module Protocol
7
+ module Commands
8
+ class ChangeModeParameters < Command
9
+ endian :little
10
+
11
+ uint32 :connection_id
12
+ latency_mode :latency_mode
13
+ uint16 :auto_disconnect_time
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ require 'flic/protocol/commands'
2
+
3
+ require 'bindata'
4
+
5
+ module Flic
6
+ module Protocol
7
+ module Commands
8
+ class Command < BinData::Record
9
+ endian :little
10
+
11
+ uint8 :opcode, initial_value: -> { init_opcode }
12
+
13
+ private
14
+
15
+ def init_opcode
16
+ Commands.opcode_for_command_class(self.class)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ require 'flic/protocol/commands'
2
+ require 'flic/protocol/commands/command'
3
+ require 'flic/protocol/primitives/bluetooth_address'
4
+ require 'flic/protocol/primitives/latency_mode'
5
+
6
+ module Flic
7
+ module Protocol
8
+ module Commands
9
+ class CreateConnectionChannel < Command
10
+ endian :little
11
+
12
+ uint32 :connection_id
13
+ bluetooth_address :bluetooth_address
14
+
15
+ latency_mode :latency_mode
16
+ uint16 :auto_disconnect_time, initial_value: 512 # 512 means disabled
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ require 'flic/protocol/commands'
2
+ require 'flic/protocol/commands/command'
3
+ require 'flic/protocol/primitives/bluetooth_address'
4
+
5
+ module Flic
6
+ module Protocol
7
+ module Commands
8
+ class CreateScanWizard < Command
9
+ endian :little
10
+
11
+ uint32 :scan_wizard_id
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require 'flic/protocol/commands'
2
+ require 'flic/protocol/commands/command'
3
+
4
+ module Flic
5
+ module Protocol
6
+ module Commands
7
+ class CreateScanner < Command
8
+ endian :little
9
+
10
+ uint32 :scan_id
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ require 'flic/protocol/commands'
2
+ require 'flic/protocol/commands/command'
3
+ require 'flic/protocol/primitives/bluetooth_address'
4
+
5
+ module Flic
6
+ module Protocol
7
+ module Commands
8
+ class ForceDisconnect < Command
9
+ endian :little
10
+
11
+ bluetooth_address :bluetooth_address
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'flic/protocol/commands'
2
+ require 'flic/protocol/commands/command'
3
+ require 'flic/protocol/primitives/bluetooth_address'
4
+
5
+ module Flic
6
+ module Protocol
7
+ module Commands
8
+ class GetButtonUuid < Command
9
+ endian :little
10
+
11
+ bluetooth_address :bluetooth_address
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ require 'flic/protocol/commands'
2
+ require 'flic/protocol/commands/command'
3
+
4
+ module Flic
5
+ module Protocol
6
+ module Commands
7
+ class GetInfo < Command
8
+ endian :little
9
+ end
10
+ end
11
+ end
12
+ end