flic 0.0.5 → 0.0.6
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 +4 -4
- data/README.md +51 -49
- data/Rakefile +6 -1
- data/flic.gemspec +1 -0
- data/lib/flic.rb +1 -0
- data/lib/flic/blocker.rb +59 -0
- data/lib/flic/client.rb +12 -7
- data/lib/flic/client/scan_wizard.rb +4 -0
- data/lib/flic/protocol.rb +20 -6
- data/lib/flic/protocol/commands.rb +7 -0
- data/lib/flic/protocol/commands/cancel_scan_wizard.rb +1 -3
- data/lib/flic/protocol/commands/change_mode_parameters.rb +1 -3
- data/lib/flic/protocol/commands/command.rb +10 -4
- data/lib/flic/protocol/commands/create_connection_channel.rb +1 -3
- data/lib/flic/protocol/commands/create_scan_wizard.rb +1 -3
- data/lib/flic/protocol/commands/create_scanner.rb +1 -3
- data/lib/flic/protocol/commands/force_disconnect.rb +0 -2
- data/lib/flic/protocol/commands/get_button_uuid.rb +0 -2
- data/lib/flic/protocol/commands/get_info.rb +0 -1
- data/lib/flic/protocol/commands/ping.rb +1 -3
- data/lib/flic/protocol/commands/remove_connection_channel.rb +1 -3
- data/lib/flic/protocol/commands/remove_scanner.rb +1 -3
- data/lib/flic/protocol/connection.rb +22 -19
- data/lib/flic/protocol/events.rb +9 -2
- data/lib/flic/protocol/events/advertisement_packet.rb +2 -4
- data/lib/flic/protocol/events/bluetooth_controller_state_change.rb +0 -2
- data/lib/flic/protocol/events/button_click_or_hold.rb +2 -4
- data/lib/flic/protocol/events/button_single_or_double_click.rb +2 -4
- data/lib/flic/protocol/events/button_single_or_double_click_or_hold.rb +2 -4
- data/lib/flic/protocol/events/button_up_or_down.rb +2 -4
- data/lib/flic/protocol/events/connection_channel_removed.rb +1 -3
- data/lib/flic/protocol/events/connection_status_changed.rb +1 -3
- data/lib/flic/protocol/events/create_connection_channel_response.rb +1 -3
- data/lib/flic/protocol/events/event.rb +11 -5
- data/lib/flic/protocol/events/get_button_uuid_response.rb +0 -2
- data/lib/flic/protocol/events/get_info_response.rb +4 -6
- data/lib/flic/protocol/events/got_space_for_new_connection.rb +1 -3
- data/lib/flic/protocol/events/new_verified_button.rb +0 -2
- data/lib/flic/protocol/events/no_space_for_new_connection.rb +1 -3
- data/lib/flic/protocol/events/ping_response.rb +1 -3
- data/lib/flic/protocol/events/scan_wizard_button_connected.rb +1 -3
- data/lib/flic/protocol/events/scan_wizard_completed.rb +1 -3
- data/lib/flic/protocol/events/scan_wizard_found_private_button.rb +1 -3
- data/lib/flic/protocol/events/scan_wizard_found_public_button.rb +1 -3
- data/lib/flic/protocol/packet_header.rb +2 -2
- data/lib/flic/protocol/primitives.rb +1 -0
- data/lib/flic/protocol/primitives/bluetooth_address.rb +2 -1
- data/lib/flic/protocol/primitives/bluetooth_address_type.rb +3 -0
- data/lib/flic/protocol/primitives/bluetooth_controller_state.rb +7 -3
- data/lib/flic/protocol/primitives/boolean.rb +1 -0
- data/lib/flic/protocol/primitives/click_type.rb +13 -6
- data/lib/flic/protocol/primitives/connection_status.rb +7 -4
- data/lib/flic/protocol/primitives/create_connection_channel_error.rb +4 -2
- data/lib/flic/protocol/primitives/device_name.rb +2 -1
- data/lib/flic/protocol/primitives/disconnect_reason.rb +8 -4
- data/lib/flic/protocol/primitives/disconnect_time.rb +8 -9
- data/lib/flic/protocol/primitives/enum.rb +12 -1
- data/lib/flic/protocol/primitives/latency_mode.rb +7 -3
- data/lib/flic/protocol/primitives/removed_reason.rb +14 -7
- data/lib/flic/protocol/primitives/scan_wizard_result.rb +15 -8
- data/lib/flic/protocol/primitives/uuid.rb +13 -3
- data/lib/flic/simple_client.rb +116 -78
- data/lib/flic/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b313acdfde6b058cfba2f4d0c148b8937e6c2ab9
|
4
|
+
data.tar.gz: c5805fd3c6421a2b688e7f4b85b7594423dbe4a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e8dd3aa2feed080bf4d36af89dd3bf532ebf00842099d08aca5a3403974a83fcc859edae2b45760d5097db6b6308277d4f194f26bf22e5b7733586a8bb25b68
|
7
|
+
data.tar.gz: 934f32212c39ef034446a6d502ffd6a7fd611e1fd751a1dba8bceaee079fe4bb455038cf93ae305db0d04ef2a46f4550b2b51cbc8865e75c89ccbfd7a215cb04
|
data/README.md
CHANGED
@@ -32,7 +32,7 @@ A button must be in public mode before it can be added. To put a button in publi
|
|
32
32
|
Similarly, a button may be disconnected by passing `Flic::SimpleClient#disconnect_button` a button's bluetooth address.
|
33
33
|
|
34
34
|
### Listening for button events
|
35
|
-
`Flic::SimpleClient#listen` accepts a latency mode (`:low`, `:normal`, or `:high`) as it's first argument and button bluetooth addresses as its other arguments. For each event that occurs to those buttons, it yields the bluetooth address of the button responsible for a given event, the type of click involved in the event (`:button_down`, `:button_up`, `:button_single_click`, `:button_double_click`, or `:button_hold`), the time in
|
35
|
+
`Flic::SimpleClient#listen` accepts a latency mode (`:low`, `:normal`, or `:high`) as it's first argument and button bluetooth addresses as its other arguments. For each event that occurs to those buttons, it yields the bluetooth address of the button responsible for a given event, the type of click involved in the event (`:button_down`, `:button_up`, `:button_single_click`, `:button_double_click`, or `:button_hold`), the time in seconds since the event occured, and whether the event was queued. **It will block until the connection is closed or the block raises some other exception.**
|
36
36
|
|
37
37
|
### Closing the connection to `flicd`
|
38
38
|
To gracefully cleanup all connection channels and close the socket connection, call `Flic::SimpleClient#shutdown`. Once a `Flic::SimpleClient` has been shutdown it will close the underlying socket and cannot be used anymore.
|
@@ -40,6 +40,7 @@ To gracefully cleanup all connection channels and close the socket connection, c
|
|
40
40
|
### Example
|
41
41
|
This is the script that I wrote to allow some of my Flic buttons to control Wink-enabled smart devices in home.
|
42
42
|
```ruby
|
43
|
+
|
43
44
|
#!/usr/bin/env ruby
|
44
45
|
|
45
46
|
require 'bundler/setup'
|
@@ -55,61 +56,62 @@ BEDROOM_BUTTON = 'XX:XX:XX:XX:XX:XX'
|
|
55
56
|
NIGHTSTAND_BUTTON = 'XX:XX:XX:XX:XX:XX'
|
56
57
|
|
57
58
|
begin
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
59
|
+
puts "[*] Opening a connection to flicd..."
|
60
|
+
client = Flic::SimpleClient.new
|
61
|
+
|
62
|
+
puts "[*] Entering main loop"
|
63
|
+
client.listen(:low, LIVING_ROOM_BUTTON, BEDROOM_BUTTON, NIGHTSTAND_BUTTON) do |button, event, latency|
|
64
|
+
if latency > 10
|
65
|
+
puts "[*] [#{button}] Ignoring #{event} because the latency is #{latency} seconds"
|
66
|
+
else
|
67
|
+
puts "[*] [#{button}] Handling #{event}"
|
68
|
+
|
69
|
+
case button
|
70
|
+
when LIVING_ROOM_BUTTON
|
71
|
+
case event
|
72
|
+
when :button_single_click
|
73
|
+
puts HTTParty.post('https://api.wink.com/scenes/SCENE_ID/activate', headers: { Authorization: "Bearer #{WINK_ACCESS_TOKEN}" }).inspect
|
74
|
+
when :button_double_click
|
75
|
+
puts HTTParty.post('https://api.wink.com/scenes/SCENE_ID/activate', headers: { Authorization: "Bearer #{WINK_ACCESS_TOKEN}" }).inspect
|
76
|
+
when :button_hold
|
77
|
+
puts HTTParty.post('https://api.wink.com/scenes/SCENE_ID/activate', headers: { Authorization: "Bearer #{WINK_ACCESS_TOKEN}" }).inspect
|
78
|
+
end
|
79
|
+
|
80
|
+
when BEDROOM_BUTTON
|
81
|
+
case event
|
82
|
+
when :button_single_click
|
83
|
+
puts HTTParty.post('https://api.wink.com/scenes/SCENE_ID/activate', headers: { Authorization: "Bearer #{WINK_ACCESS_TOKEN}" }).inspect
|
84
|
+
when :button_double_click
|
85
|
+
puts HTTParty.post('https://api.wink.com/scenes/SCENE_ID/activate', headers: { Authorization: "Bearer #{WINK_ACCESS_TOKEN}" }).inspect
|
86
|
+
when :button_hold
|
87
|
+
puts HTTParty.post('https://api.wink.com/scenes/SCENE_ID/activate', headers: { Authorization: "Bearer #{WINK_ACCESS_TOKEN}" }).inspect
|
88
|
+
end
|
89
|
+
|
90
|
+
when NIGHTSTAND_BUTTON
|
91
|
+
case event
|
92
|
+
when :button_single_click
|
93
|
+
puts HTTParty.post('https://api.wink.com/scenes/SCENE_ID/activate', headers: { Authorization: "Bearer #{WINK_ACCESS_TOKEN}" }).inspect
|
94
|
+
when :button_double_click
|
95
|
+
puts HTTParty.post('https://api.wink.com/scenes/SCENE_ID/activate', headers: { Authorization: "Bearer #{WINK_ACCESS_TOKEN}" }).inspect
|
96
|
+
when :button_hold
|
97
|
+
puts HTTParty.post('https://api.wink.com/scenes/SCENE_ID/activate', headers: { Authorization: "Bearer #{WINK_ACCESS_TOKEN}" }).inspect
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
101
102
|
rescue StandardError => error
|
102
|
-
|
103
|
-
|
103
|
+
puts "[!] Whoops! #{error.inspect} occured. Wait for a second and restart everything."
|
104
|
+
sleep 1
|
104
105
|
|
105
|
-
|
106
|
+
retry
|
106
107
|
rescue Interrupt
|
107
|
-
|
108
|
+
puts "[*] Shutting down gracefully because of an interrupt"
|
108
109
|
|
109
|
-
|
110
|
+
client.shutdown
|
110
111
|
end
|
111
112
|
|
112
113
|
puts "[*] Goodbye cruel world!"
|
114
|
+
|
113
115
|
```
|
114
116
|
|
115
117
|
## Advanced Usage
|
data/Rakefile
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core/rake_task'
|
3
|
+
require 'yard'
|
3
4
|
|
4
5
|
RSpec::Core::RakeTask.new(:spec)
|
5
|
-
task :default => :spec
|
6
6
|
task :test => :spec
|
7
7
|
|
8
|
+
YARD::Rake::YardocTask.new(:yard)
|
9
|
+
task :doc => :yard
|
10
|
+
|
8
11
|
task :console do
|
9
12
|
require 'flic'
|
10
13
|
require 'pry'
|
11
14
|
Pry.start
|
12
15
|
end
|
16
|
+
|
17
|
+
task :default => [:spec, :doc]
|
data/flic.gemspec
CHANGED
data/lib/flic.rb
CHANGED
data/lib/flic/blocker.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'flic'
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
module Flic
|
6
|
+
class Blocker
|
7
|
+
attr_reader :rejection_value
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@semaphore = Mutex.new
|
11
|
+
@queues = []
|
12
|
+
@rejection_value = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def block_until_callback
|
16
|
+
queue = Queue.new
|
17
|
+
|
18
|
+
begin
|
19
|
+
@semaphore.synchronize do
|
20
|
+
if @queues.frozen?
|
21
|
+
raise *rejection_value
|
22
|
+
else
|
23
|
+
@queues << queue
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
yield proc { |value| queue << [:resolve, value] }
|
28
|
+
|
29
|
+
control, value = queue.pop
|
30
|
+
|
31
|
+
case control
|
32
|
+
when :resolve
|
33
|
+
value
|
34
|
+
when :reject
|
35
|
+
raise *rejection_value
|
36
|
+
end
|
37
|
+
ensure
|
38
|
+
@semaphore.synchronize do
|
39
|
+
unless @queues.frozen?
|
40
|
+
@queues.delete queue
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def unblock_all!(*rejection_value)
|
47
|
+
@semaphore.synchronize do
|
48
|
+
unless @queues.frozen?
|
49
|
+
@rejection_value = rejection_value
|
50
|
+
|
51
|
+
@queues.each { |queue| queue << [:reject, *rejection_value] }.clear
|
52
|
+
@queues.freeze
|
53
|
+
|
54
|
+
freeze
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/flic/client.rb
CHANGED
@@ -6,7 +6,7 @@ require 'socket'
|
|
6
6
|
module Flic
|
7
7
|
class Client
|
8
8
|
class Error < StandardError; end
|
9
|
-
class
|
9
|
+
class Shutdown < Error; end
|
10
10
|
|
11
11
|
autoload :ConnectionChannel, 'flic/client/connection_channel'
|
12
12
|
autoload :Features, 'flic/client/features'
|
@@ -41,27 +41,32 @@ module Flic
|
|
41
41
|
define_callbacks :new_button_verified, :bluetooth_controller_state_changed,
|
42
42
|
:connections_exhausted, :connection_available
|
43
43
|
|
44
|
-
|
45
44
|
def initialize(host = 'localhost', port = 5551)
|
46
45
|
@host, @port = host, port
|
47
46
|
@handle_next_event_semaphore = Mutex.new
|
48
47
|
@socket = TCPSocket.new(host, port)
|
49
48
|
@connection = Protocol::Connection.new(socket)
|
49
|
+
@is_shutdown = false
|
50
50
|
yield self if block_given?
|
51
51
|
end
|
52
52
|
|
53
|
+
def shutdown?
|
54
|
+
@is_shutdown
|
55
|
+
end
|
56
|
+
|
53
57
|
def shutdown
|
54
|
-
|
58
|
+
socket.close
|
59
|
+
@is_shutdown = true
|
55
60
|
end
|
56
61
|
|
57
62
|
def handle_next_event
|
58
63
|
@handle_next_event_semaphore.synchronize do
|
59
64
|
begin
|
60
65
|
handle_event connection.recv_event
|
61
|
-
rescue Protocol::Connection::
|
66
|
+
rescue Protocol::Connection::UnderlyingSocketClosedError
|
62
67
|
shutdown
|
63
68
|
|
64
|
-
raise
|
69
|
+
raise Shutdown, 'The connection has been closed'
|
65
70
|
end
|
66
71
|
end
|
67
72
|
end
|
@@ -74,10 +79,10 @@ module Flic
|
|
74
79
|
|
75
80
|
def send_command(command)
|
76
81
|
connection.send_command(command)
|
77
|
-
rescue Protocol::Connection::
|
82
|
+
rescue Protocol::Connection::UnderlyingSocketClosedError
|
78
83
|
shutdown
|
79
84
|
|
80
|
-
raise
|
85
|
+
raise Shutdown, 'The connection has been closed'
|
81
86
|
end
|
82
87
|
|
83
88
|
def handle_event(event)
|
data/lib/flic/protocol.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'flic'
|
2
2
|
|
3
3
|
module Flic
|
4
|
+
# This module contains an implementation of the Flic binary protocol. Of particular external interest is
|
5
|
+
# `Flic::Protocol::Connection` which provides a wrapper for the binary protocol around a socket instance.
|
4
6
|
module Protocol
|
7
|
+
extend self
|
8
|
+
|
5
9
|
class Error < StandardError; end
|
6
10
|
|
7
11
|
autoload :Commands, 'flic/protocol/commands'
|
@@ -10,9 +14,10 @@ module Flic
|
|
10
14
|
autoload :PacketHeader, 'flic/protocol/packet_header'
|
11
15
|
autoload :Primitives, 'flic/protocol/primitives'
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
# Serializes an instance of a protocol command class to a binary string
|
18
|
+
# @param command [Flic::Protocol::Commands::Command]
|
19
|
+
# @return [String] binary string
|
20
|
+
def serialize_command(command)
|
16
21
|
case command
|
17
22
|
when Commands::Command
|
18
23
|
command.to_binary_s
|
@@ -23,7 +28,10 @@ module Flic
|
|
23
28
|
raise Error, "Cannot serialize command `#{command.inspect}`"
|
24
29
|
end
|
25
30
|
|
26
|
-
|
31
|
+
# Deserializes an instance of a protocol command class from a binary string
|
32
|
+
# @param serialized_command [String] binary string
|
33
|
+
# @return [Flic::Protocol::Commands::Command]
|
34
|
+
def parse_command(serialized_command)
|
27
35
|
command = Commands::Command.read(serialized_command)
|
28
36
|
opcode = command.opcode
|
29
37
|
command_class = Commands::Command.command_class_for_opcode(opcode)
|
@@ -37,7 +45,10 @@ module Flic
|
|
37
45
|
raise Error, "Cannot parse event `#{serialized_command.inspect}`"
|
38
46
|
end
|
39
47
|
|
40
|
-
|
48
|
+
# Serializes an instance of a protocol event class to a binary string
|
49
|
+
# @param event [Flic::Protocol::Events::Event]
|
50
|
+
# @return [String] binary string
|
51
|
+
def serialize_event(event)
|
41
52
|
case event
|
42
53
|
when Commands::Event
|
43
54
|
event.to_binary_s
|
@@ -48,7 +59,10 @@ module Flic
|
|
48
59
|
raise Error, "Cannot serialize event `#{event.inspect}`"
|
49
60
|
end
|
50
61
|
|
51
|
-
|
62
|
+
# Deserializes an instance of a protocol event class from a binary string
|
63
|
+
# @param serialized_event [String] binary string
|
64
|
+
# @return [Flic::Protocol::Events::Event]
|
65
|
+
def parse_event(serialized_event)
|
52
66
|
event = Events::Event.read(serialized_event)
|
53
67
|
opcode = event.opcode
|
54
68
|
event_class = Events.event_class_for_opcode(opcode)
|
@@ -2,6 +2,7 @@ require 'flic/protocol'
|
|
2
2
|
|
3
3
|
module Flic
|
4
4
|
module Protocol
|
5
|
+
# A namespace module for all of the command classes
|
5
6
|
module Commands
|
6
7
|
autoload :CancelScanWizard, 'flic/protocol/commands/cancel_scan_wizard'
|
7
8
|
autoload :ChangeModeParameters, 'flic/protocol/commands/change_mode_parameters'
|
@@ -32,10 +33,16 @@ module Flic
|
|
32
33
|
|
33
34
|
OPCODE_COMMAND_CLASS = COMMAND_CLASS_OPCODE.invert.freeze
|
34
35
|
|
36
|
+
# Finds the command class for a given opcode
|
37
|
+
# @param opcode [Integer]
|
38
|
+
# @return [Class]
|
35
39
|
def self.command_class_for_opcode(opcode)
|
36
40
|
OPCODE_COMMAND_CLASS[opcode]
|
37
41
|
end
|
38
42
|
|
43
|
+
# Finds the opcode for a given command class
|
44
|
+
# @param command_class [Class]
|
45
|
+
# @return [Integer]
|
39
46
|
def self.opcode_for_command_class(command_class)
|
40
47
|
COMMAND_CLASS_OPCODE[command_class]
|
41
48
|
end
|
@@ -6,15 +6,21 @@ module Flic
|
|
6
6
|
module Protocol
|
7
7
|
module Commands
|
8
8
|
class Command < BinData::Record
|
9
|
-
|
10
|
-
|
11
|
-
uint8 :opcode, initial_value: -> { init_opcode }
|
9
|
+
uint8le :opcode, initial_value: :class_opcode, assert: :opcode_matcher
|
12
10
|
|
13
11
|
private
|
14
12
|
|
15
|
-
def
|
13
|
+
def class_opcode
|
16
14
|
Commands.opcode_for_command_class(self.class)
|
17
15
|
end
|
16
|
+
|
17
|
+
def opcode_matcher
|
18
|
+
if class_opcode
|
19
|
+
class_opcode
|
20
|
+
else
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
18
24
|
end
|
19
25
|
end
|
20
26
|
end
|