flic 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -4,9 +4,9 @@ require 'bindata'
|
|
4
4
|
|
5
5
|
module Flic
|
6
6
|
module Protocol
|
7
|
+
# Every packet starts with a packet header that includes the length of the remaining packet
|
7
8
|
class PacketHeader < BinData::Record
|
8
|
-
|
9
|
-
uint16 :byte_length
|
9
|
+
uint16le :byte_length
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -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 primitive classes
|
5
6
|
module Primitives
|
6
7
|
autoload :BluetoothAddress, 'flic/protocol/primitives/bluetooth_address'
|
7
8
|
autoload :BluetoothAddressType, 'flic/protocol/primitives/bluetooth_address_type'
|
@@ -6,11 +6,12 @@ require 'scanf'
|
|
6
6
|
module Flic
|
7
7
|
module Protocol
|
8
8
|
module Primitives
|
9
|
+
# A bluetooth address (bdaddr_t) is encoded in little endan, 6 bytes in total. When such an address is written as a string, it is normally written in big endian, where each byte is encoded in hex and colon as separator for each byte. For example, the address 08:09:0a:0b:0c:0d is encoded as the bytes 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08.
|
9
10
|
class BluetoothAddress < BinData::Primitive
|
10
11
|
PRINTF_FORMAT_STRING = '%.2X:%.2X:%.2X:%.2X:%.2X:%.2X'.freeze
|
11
12
|
SCANF_FORMAT_STRING = '%X:%X:%X:%X:%X:%X'.freeze
|
12
13
|
|
13
|
-
array :little_endian_octets, type: :
|
14
|
+
array :little_endian_octets, type: :uint8le, initial_length: 6
|
14
15
|
|
15
16
|
def get
|
16
17
|
sprintf(PRINTF_FORMAT_STRING, *big_endian_octets)
|
@@ -4,6 +4,9 @@ require 'flic/protocol/primitives/enum'
|
|
4
4
|
module Flic
|
5
5
|
module Protocol
|
6
6
|
module Primitives
|
7
|
+
# The server can be configured to either use the burnt-in public address stored inside the bluetooth controller, or to use a custom random static address. This custom address is a good idea if you want to be able to use your database with bonding information with a different bluetooth controller.
|
8
|
+
# [:public_bluetooth_address_type] burnt-in public address
|
9
|
+
# [:random_bluetooth_address_type] another address
|
7
10
|
class BluetoothAddressType < Enum
|
8
11
|
option :public_bluetooth_address_type
|
9
12
|
option :random_bluetooth_address_type
|
@@ -4,10 +4,14 @@ require 'flic/protocol/primitives/enum'
|
|
4
4
|
module Flic
|
5
5
|
module Protocol
|
6
6
|
module Primitives
|
7
|
+
# The server software detects when the bluetooth controller is removed or is made unavailable. It will then repeatedly retry to re-established a connection to the same bluetooth controller.
|
8
|
+
# [:detached] The server software has lost the HCI socket to the bluetooth controller and is trying to reconnect.
|
9
|
+
# [:resetting] The server software has just got connected to the HCI socket and initiated a reset of the bluetooth controller.
|
10
|
+
# [:attached] The bluetooth controller has done initialization and is up and running.
|
7
11
|
class BluetoothControllerState < Enum
|
8
|
-
option :detached
|
9
|
-
option :resetting
|
10
|
-
option :attached
|
12
|
+
option :detached
|
13
|
+
option :resetting
|
14
|
+
option :attached
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
@@ -4,13 +4,20 @@ require 'flic/protocol/primitives/enum'
|
|
4
4
|
module Flic
|
5
5
|
module Protocol
|
6
6
|
module Primitives
|
7
|
+
# The type of click registered by a button
|
8
|
+
# [:button_down] The button was pressed.
|
9
|
+
# [:button_up] The button was released.
|
10
|
+
# [:button_click] The button was clicked, and was held for at most 1 second between press and release.
|
11
|
+
# [:button_single_click] The button was clicked once.
|
12
|
+
# [:button_double_click] The button was clicked twice. The time between the first and second press must be at most 0.5 seconds.
|
13
|
+
# [:button_hold] The button was held for at least 1 second.
|
7
14
|
class ClickType < Enum
|
8
|
-
option :button_down
|
9
|
-
option :button_up
|
10
|
-
option :button_click
|
11
|
-
option :button_single_click
|
12
|
-
option :button_double_click
|
13
|
-
option :button_hold
|
15
|
+
option :button_down
|
16
|
+
option :button_up
|
17
|
+
option :button_click
|
18
|
+
option :button_single_click
|
19
|
+
option :button_double_click
|
20
|
+
option :button_hold
|
14
21
|
end
|
15
22
|
end
|
16
23
|
end
|
@@ -4,10 +4,13 @@ require 'flic/protocol/primitives/enum'
|
|
4
4
|
module Flic
|
5
5
|
module Protocol
|
6
6
|
module Primitives
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
# [:disconnected] Not currently an established connection, but will connect as soon as the button is pressed and it is in range as long as the connection channel hasn't been removed (and unless maximum number of concurrent connections has been reached or the bluetooth controller has been detached).
|
8
|
+
# [:connected] The physical bluetooth connection has just been established and the server and the button are currently verifying each other. As soon as this is done, it will switch to the ready status.
|
9
|
+
# [:ready] The verification is done and button events may now arrive.
|
10
|
+
class ConnectionStatus < Enum
|
11
|
+
option :disconnected
|
12
|
+
option :connected
|
13
|
+
option :ready
|
11
14
|
end
|
12
15
|
end
|
13
16
|
end
|
@@ -4,9 +4,11 @@ require 'flic/protocol/primitives/enum'
|
|
4
4
|
module Flic
|
5
5
|
module Protocol
|
6
6
|
module Primitives
|
7
|
+
# :no_error - There were space in the bluetooth controller's white list to accept a physical pending connection for this button
|
8
|
+
# :maximum_pending_connections_reached - There were no space left in the bluetooth controller to allow a new pending connection
|
7
9
|
class CreateConnectionChannelError < Enum
|
8
|
-
option :no_error
|
9
|
-
option :maximum_pending_connections_reached
|
10
|
+
option :no_error
|
11
|
+
option :maximum_pending_connections_reached
|
10
12
|
end
|
11
13
|
end
|
12
14
|
end
|
@@ -5,11 +5,12 @@ require 'bindata'
|
|
5
5
|
module Flic
|
6
6
|
module Protocol
|
7
7
|
module Primitives
|
8
|
+
# The name of a device (up to 16 character string)
|
8
9
|
class DeviceName < BinData::Primitive
|
9
10
|
BYTE_LENGTH = 16
|
10
11
|
|
11
12
|
uint8 :byte_length
|
12
|
-
array :bytes, type: :
|
13
|
+
array :bytes, type: :int8le, initial_length: BYTE_LENGTH
|
13
14
|
|
14
15
|
def get
|
15
16
|
''.tap do |string|
|
@@ -4,11 +4,15 @@ require 'flic/protocol/primitives/enum'
|
|
4
4
|
module Flic
|
5
5
|
module Protocol
|
6
6
|
module Primitives
|
7
|
+
# [:unspecified] Unknown reason
|
8
|
+
# [:connection_establishment_failed] The bluetooth controller established a connection, but the Flic button didn't answer in time.
|
9
|
+
# [:timed_out] The connection to the Flic button was lost due to either being out of range or some radio communication problems.
|
10
|
+
# [:bonding_keys_mismatch] The server and the Flic button for some reason don't agree on the previously established bonding keys.
|
7
11
|
class DisconnectReason < Enum
|
8
|
-
option :unspecified
|
9
|
-
option :connection_establishment_failed
|
10
|
-
option :timed_out
|
11
|
-
option :bonding_keys_mismatch
|
12
|
+
option :unspecified
|
13
|
+
option :connection_establishment_failed
|
14
|
+
option :timed_out
|
15
|
+
option :bonding_keys_mismatch
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -3,13 +3,12 @@ require 'flic/protocol/primitives'
|
|
3
3
|
module Flic
|
4
4
|
module Protocol
|
5
5
|
module Primitives
|
6
|
+
# Time in seconds after the Flic button may disconnect after the latest press or release. The button will reconnect automatically when it is later pressed again and deliver its enqueued events. Valid values are 0 - 511.
|
6
7
|
class DisconnectTime < BinData::Primitive
|
7
|
-
|
8
|
-
|
9
|
-
uint16 :time, initial_value: 512
|
8
|
+
uint16le :time, initial_value: 511
|
10
9
|
|
11
10
|
def get
|
12
|
-
if time ==
|
11
|
+
if time == 511
|
13
12
|
nil
|
14
13
|
else
|
15
14
|
time
|
@@ -17,12 +16,12 @@ module Flic
|
|
17
16
|
end
|
18
17
|
|
19
18
|
def set(value)
|
20
|
-
if value ==
|
21
|
-
|
22
|
-
elsif value
|
23
|
-
|
19
|
+
if value == nil
|
20
|
+
self.time = 511
|
21
|
+
elsif value >= 511
|
22
|
+
raise RangeError, 'disconnect_time must be less than 511 seconds (or nil for never)'
|
24
23
|
else
|
25
|
-
self.time =
|
24
|
+
self.time = value
|
26
25
|
end
|
27
26
|
end
|
28
27
|
end
|
@@ -5,32 +5,39 @@ require 'bindata'
|
|
5
5
|
module Flic
|
6
6
|
module Protocol
|
7
7
|
module Primitives
|
8
|
+
# An abstract class for 1 byte enums
|
8
9
|
class Enum < BinData::Primitive
|
9
10
|
class Error < StandardError; end
|
10
11
|
class InvalidOptionError < Error; end
|
11
12
|
class InvalidOctetError < Error; end
|
12
13
|
|
13
14
|
class << self
|
15
|
+
# @return [Hash] a map of options to byte values
|
14
16
|
def option_octet
|
15
17
|
@option_octet ||= {}
|
16
18
|
end
|
17
19
|
|
20
|
+
# @return [Hash] a map of byte values to options
|
18
21
|
def octet_option
|
19
22
|
@octet_option ||= {}
|
20
23
|
end
|
21
24
|
|
25
|
+
# @return [Array] the valid options for this enum
|
22
26
|
def options
|
23
27
|
option_octet.keys
|
24
28
|
end
|
25
29
|
|
30
|
+
# @return [Array] the valid byte values for this enum
|
26
31
|
def octets
|
27
32
|
octet_option.keys
|
28
33
|
end
|
29
34
|
|
35
|
+
# @return [Integer] the byte value for the option with the largest byte value
|
30
36
|
def max_octet
|
31
37
|
octets.max
|
32
38
|
end
|
33
39
|
|
40
|
+
# @return [Integer] the next available byte value (starting at 0x00)
|
34
41
|
def next_available_octet
|
35
42
|
if max_octet
|
36
43
|
1 + max_octet
|
@@ -41,18 +48,22 @@ module Flic
|
|
41
48
|
|
42
49
|
private
|
43
50
|
|
51
|
+
# Associates an option with a byte value of the enum
|
52
|
+
# @param option [Symbol]
|
53
|
+
# @param octet [Integer] (defaults to the next available byte value)
|
44
54
|
def option(option, octet = next_available_octet)
|
45
55
|
option_octet[option] = octet
|
46
56
|
octet_option[octet] = option
|
47
57
|
end
|
48
58
|
|
59
|
+
# Associates a byte value of an enum with an option
|
49
60
|
def octet(octet, option)
|
50
61
|
octet_option[octet] = option
|
51
62
|
option_octet[option] = octet
|
52
63
|
end
|
53
64
|
end
|
54
65
|
|
55
|
-
|
66
|
+
uint8le :octet
|
56
67
|
|
57
68
|
def get
|
58
69
|
if octet_option.has_key?(octet)
|
@@ -4,10 +4,14 @@ require 'flic/protocol/primitives/enum'
|
|
4
4
|
module Flic
|
5
5
|
module Protocol
|
6
6
|
module Primitives
|
7
|
+
# This specifies the accepted latency mode for the corresponding connection channel. The physical bluetooth connection will use the lowest mode set by any connection channel. The battery usage for the Flic button is normally about the same for all modes if the connection is stable. However lower modes will have higher battery usage if the connection is unstable. Lower modes also consumes more power for the client, which is normally not a problem since most computers run on wall power or have large batteries.
|
8
|
+
# [:normal] Up to 100 ms latency.
|
9
|
+
# [:low] Up to 17.5 ms latency.
|
10
|
+
# [:high] Up to 275 ms latency.
|
7
11
|
class LatencyMode < Enum
|
8
|
-
option :normal
|
9
|
-
option :low
|
10
|
-
option :high
|
12
|
+
option :normal
|
13
|
+
option :low
|
14
|
+
option :high
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
@@ -4,14 +4,21 @@ require 'flic/protocol/primitives/enum'
|
|
4
4
|
module Flic
|
5
5
|
module Protocol
|
6
6
|
module Primitives
|
7
|
+
# [:removed_by_this_client] The connection channel was removed by this client.
|
8
|
+
# [:force_disconnected_by_this_client] The connection channel was removed due to a force disconnect by this client.
|
9
|
+
# [:force_disconnected_by_other_client] Another client force disconnected the button used in this connection channel.
|
10
|
+
# [:button_is_private] The button is not in public mode. Hold it down for 7 seconds while not trying to establish a connection, then try to reconnect by creating a new connection channel.
|
11
|
+
# [:verify_timeout] After the connection was established, the bonding procedure didn't complete in time.
|
12
|
+
# [:internet_backend_error] The internet request to the Flic backend failed.
|
13
|
+
# [:invalid_data] According to the Flic backend, this Flic button supplied invalid identity data.
|
7
14
|
class RemovedReason < Enum
|
8
|
-
option :removed_by_this_client
|
9
|
-
option :force_disconnected_by_this_client
|
10
|
-
option :force_disconnected_by_other_client
|
11
|
-
option :button_is_private
|
12
|
-
option :verify_timeout
|
13
|
-
option :internet_backend_error
|
14
|
-
option :invalid_data
|
15
|
+
option :removed_by_this_client
|
16
|
+
option :force_disconnected_by_this_client
|
17
|
+
option :force_disconnected_by_other_client
|
18
|
+
option :button_is_private
|
19
|
+
option :verify_timeout
|
20
|
+
option :internet_backend_error
|
21
|
+
option :invalid_data
|
15
22
|
end
|
16
23
|
end
|
17
24
|
end
|
@@ -4,14 +4,21 @@ require 'flic/protocol/primitives/enum'
|
|
4
4
|
module Flic
|
5
5
|
module Protocol
|
6
6
|
module Primitives
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
# [:success] Indicates that a button was successfully paired and verified. You may now create a connection channel to that button.
|
8
|
+
# [:cancelled_by_user] A CmdCancelScanWizard was sent.
|
9
|
+
# [:timeout] The scan wizard did not make any progress for some time. Current timeouts are 20 seconds for finding any button, 20 seconds for finding a public button (in case of a private button was found), 10 seconds for connecting the button, 30 seconds for pairing and verifying the button.
|
10
|
+
# [:button_private] First the button was advertising public status, but after connecting it reports private. Probably it switched from public to private just when the connection attempt was started.
|
11
|
+
# [:bluetooth_unavailable] The bluetooth controller is not attached.
|
12
|
+
# [:internet_backend_error] The internet request to the Flic backend failed.
|
13
|
+
# [:invalid_data] According to the Flic backend, this Flic button supplied invalid identity data.
|
14
|
+
class ScanWizardResult < Enum
|
15
|
+
option :success
|
16
|
+
option :cancelled_by_user
|
17
|
+
option :timeout
|
18
|
+
option :button_private
|
19
|
+
option :bluetooth_unavailable
|
20
|
+
option :internet_backend_error
|
21
|
+
option :invalid_data
|
15
22
|
end
|
16
23
|
end
|
17
24
|
end
|
@@ -6,18 +6,28 @@ require 'scanf'
|
|
6
6
|
module Flic
|
7
7
|
module Protocol
|
8
8
|
module Primitives
|
9
|
+
# The uuid of a button (nil is represented as all zeros)
|
9
10
|
class Uuid < BinData::Primitive
|
11
|
+
NULL_UUID_OCTETS = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].freeze
|
10
12
|
PRINTF_FORMAT_STRING = '%.2X%.2X%.2X%.2X-%.2X%.2X-%.2X%.2X-%.2X%.2X-%.2X%.2X%.2X%.2X%.2X%.2X'.freeze
|
11
13
|
SCANF_FORMAT_STRING = '%X%X%X%X-%X%X-%X%X-%X%X-%X%X%X%X%X%X'.freeze
|
12
14
|
|
13
|
-
array :octets, type: :
|
15
|
+
array :octets, type: :uint8le, initial_length: 16
|
14
16
|
|
15
17
|
def get
|
16
|
-
|
18
|
+
if NULL_UUID_OCTETS == octets
|
19
|
+
nil
|
20
|
+
else
|
21
|
+
sprintf(PRINTF_FORMAT_STRING, *octets)
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
25
|
def set(value)
|
20
|
-
|
26
|
+
if value
|
27
|
+
self.octets = value.scanf(SCANF_FORMAT_STRING)
|
28
|
+
else
|
29
|
+
self.octets = NULL_UUID_OCTETS
|
30
|
+
end
|
21
31
|
end
|
22
32
|
end
|
23
33
|
end
|
data/lib/flic/simple_client.rb
CHANGED
@@ -5,121 +5,159 @@ require 'thread'
|
|
5
5
|
module Flic
|
6
6
|
class SimpleClient
|
7
7
|
class Error < StandardError; end
|
8
|
-
class
|
9
|
-
|
10
|
-
|
8
|
+
class Shutdown < Error; end
|
9
|
+
class ConnectionChannelRemoved < Error; end
|
10
|
+
class ButtonIsPrivateError < Error; end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
attr_reader :host, :port, :thread
|
13
|
+
|
14
|
+
def initialize(host = 'localhost', port = 5551)
|
15
|
+
@host, @port = host, port
|
16
|
+
|
17
|
+
@blocker = Blocker.new
|
18
|
+
@client = Client.new(host, port)
|
19
|
+
|
20
|
+
@listen_queues_semaphore = Mutex.new
|
21
|
+
@listen_queues = []
|
22
|
+
|
23
|
+
@thread = Thread.new do
|
24
|
+
begin
|
25
|
+
@client.enter_main_loop
|
26
|
+
rescue Client::Shutdown
|
27
|
+
nil
|
28
|
+
ensure
|
29
|
+
shutdown
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@is_shutdown = false
|
34
|
+
end
|
35
|
+
|
36
|
+
def shutdown?
|
37
|
+
@is_shutdown
|
15
38
|
end
|
16
39
|
|
17
40
|
def shutdown
|
18
|
-
@
|
19
|
-
|
41
|
+
@listen_queues_semaphore.synchronize do
|
42
|
+
unless @listen_queues.frozen?
|
43
|
+
@listen_queues.each { |queue| queue << :shutdown }.clear
|
44
|
+
@listen_queues.freeze
|
45
|
+
end
|
20
46
|
end
|
47
|
+
|
48
|
+
@blocker.unblock_all! Shutdown, 'The client has shutdown'
|
49
|
+
|
50
|
+
@client.shutdown
|
51
|
+
|
52
|
+
@thread.join unless Thread.current == @thread
|
53
|
+
|
54
|
+
@is_shutdown = true
|
21
55
|
end
|
22
56
|
|
23
57
|
def buttons
|
24
|
-
@
|
25
|
-
|
26
|
-
|
58
|
+
@blocker.block_until_callback do |callback|
|
59
|
+
@client.get_info do |server_info|
|
60
|
+
callback.call server_info.verified_buttons_bluetooth_addresses
|
27
61
|
end
|
28
|
-
|
29
|
-
server_info.verified_buttons_bluetooth_addresses
|
30
62
|
end
|
63
|
+
rescue Client::Shutdown
|
64
|
+
raise Shutdown, 'The client has shutdown'
|
31
65
|
end
|
32
66
|
|
33
67
|
def connect_button
|
34
|
-
|
35
|
-
|
68
|
+
scan_wizard = Client::ScanWizard.new
|
69
|
+
saw_only_private_button = false
|
36
70
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
callback.call(bluetooth_address)
|
42
|
-
else
|
43
|
-
callback.call(nil)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
client.add_scan_wizard(scan_wizard)
|
71
|
+
begin
|
72
|
+
@blocker.block_until_callback do |callback|
|
73
|
+
scan_wizard.found_private_button do
|
74
|
+
saw_only_private_button = true
|
48
75
|
end
|
49
|
-
|
50
|
-
|
76
|
+
|
77
|
+
scan_wizard.found_public_button do
|
78
|
+
saw_only_private_button = false
|
79
|
+
end
|
80
|
+
|
81
|
+
scan_wizard.removed do
|
82
|
+
callback.call
|
83
|
+
end
|
84
|
+
|
85
|
+
@client.add_scan_wizard(scan_wizard)
|
51
86
|
end
|
87
|
+
ensure
|
88
|
+
@client.remove_scan_wizard(scan_wizard)
|
52
89
|
end
|
90
|
+
|
91
|
+
if scan_wizard.successful?
|
92
|
+
scan_wizard.button_bluetooth_address
|
93
|
+
elsif saw_only_private_button
|
94
|
+
raise ButtonIsPrivateError, 'A button was found, but it is private. Press and hold the button for 7 seconds to make it public and try again.'
|
95
|
+
end
|
96
|
+
rescue Client::Shutdown
|
97
|
+
raise Shutdown, 'The client has shutdown'
|
53
98
|
end
|
54
99
|
|
55
100
|
def disconnect_button(button_bluetooth_address)
|
56
|
-
@
|
57
|
-
|
58
|
-
|
101
|
+
@client.force_disconnect(button_bluetooth_address)
|
102
|
+
rescue Client::Shutdown
|
103
|
+
raise Shutdown, 'The client has shutdown'
|
59
104
|
end
|
60
105
|
|
61
|
-
def listen(
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
106
|
+
def listen(button_bluetooth_address_or_latency_mode, *button_bluetooth_addresses)
|
107
|
+
if Symbol === button_bluetooth_address_or_latency_mode
|
108
|
+
latency_mode = button_bluetooth_address_or_latency_mode
|
109
|
+
else
|
110
|
+
latency_mode = :normal
|
111
|
+
button_bluetooth_addresses.unshift button_bluetooth_address_or_latency_mode
|
112
|
+
end
|
66
113
|
|
67
|
-
|
68
|
-
|
69
|
-
connection_channel = Client::ConnectionChannel.new(button_bluetooth_addresses, latency_mode)
|
114
|
+
connection_channels = []
|
115
|
+
queue = Queue.new
|
70
116
|
|
71
|
-
|
72
|
-
button_events << [button_bluetooth_addresses, click_type, latency]
|
73
|
-
end
|
117
|
+
@listen_queues_semaphore.synchronize { @listen_queues << queue }
|
74
118
|
|
75
|
-
|
76
|
-
|
77
|
-
|
119
|
+
begin
|
120
|
+
button_bluetooth_addresses.each do |button_bluetooth_addresses|
|
121
|
+
connection_channel = Client::ConnectionChannel.new(button_bluetooth_addresses, latency_mode)
|
78
122
|
|
79
|
-
|
80
|
-
|
81
|
-
|
123
|
+
connection_channel.button_up_or_down do |click_type, latency|
|
124
|
+
queue << [:button_interaction, button_bluetooth_addresses, click_type, latency]
|
125
|
+
end
|
82
126
|
|
83
|
-
|
127
|
+
connection_channel.button_single_click_or_double_click_or_hold do |click_type, latency|
|
128
|
+
queue << [:button_interaction, button_bluetooth_addresses, click_type, latency]
|
129
|
+
end
|
84
130
|
|
85
|
-
|
131
|
+
connection_channel.removed do
|
132
|
+
queue << [:connection_channel_removed, connection_channel]
|
86
133
|
end
|
87
134
|
|
88
|
-
|
89
|
-
client.handle_next_event while !broken && button_events.empty?
|
135
|
+
connection_channels << connection_channel
|
90
136
|
|
91
|
-
|
92
|
-
|
93
|
-
end
|
137
|
+
@client.add_connection_channel connection_channel
|
138
|
+
end
|
94
139
|
|
95
|
-
|
140
|
+
loop do
|
141
|
+
event_type, *params = queue.pop
|
96
142
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
143
|
+
case event_type
|
144
|
+
when :button_interaction
|
145
|
+
yield *params
|
146
|
+
when :connection_channel_removed
|
147
|
+
raise ConnectionChannelRemoved, 'A connection channel was removed'
|
148
|
+
when :shutdown
|
149
|
+
raise Shutdown, 'The client has shutdown'
|
102
150
|
end
|
103
151
|
end
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
def process_events_until
|
110
|
-
done = false
|
111
|
-
result = nil
|
152
|
+
ensure
|
153
|
+
connection_channels.each do |connection_channel|
|
154
|
+
@client.remove_connection_channel connection_channel
|
155
|
+
end
|
112
156
|
|
113
|
-
|
114
|
-
done = true
|
115
|
-
result = _result
|
157
|
+
@listen_queues_semaphore.synchronize { @listen_queues.delete queue unless @listen_queues.frozen? }
|
116
158
|
end
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
client.handle_next_event until done
|
121
|
-
|
122
|
-
result
|
159
|
+
rescue Client::Shutdown
|
160
|
+
raise Shutdown, 'The client has shutdown'
|
123
161
|
end
|
124
162
|
end
|
125
163
|
end
|