rble 0.7.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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +169 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +514 -0
  5. data/exe/rble +14 -0
  6. data/ext/macos_ble/Package.swift +20 -0
  7. data/ext/macos_ble/Sources/RBLEHelper/BLEManager.swift +783 -0
  8. data/ext/macos_ble/Sources/RBLEHelper/Protocol.swift +173 -0
  9. data/ext/macos_ble/Sources/RBLEHelper/main.swift +645 -0
  10. data/ext/macos_ble/extconf.rb +73 -0
  11. data/lib/rble/backend/base.rb +181 -0
  12. data/lib/rble/backend/bluez.rb +1279 -0
  13. data/lib/rble/backend/corebluetooth.rb +653 -0
  14. data/lib/rble/backend.rb +193 -0
  15. data/lib/rble/bluez/adapter.rb +169 -0
  16. data/lib/rble/bluez/async_call.rb +85 -0
  17. data/lib/rble/bluez/async_connection_operations.rb +492 -0
  18. data/lib/rble/bluez/async_gatt_operations.rb +249 -0
  19. data/lib/rble/bluez/async_introspection.rb +151 -0
  20. data/lib/rble/bluez/dbus_connection.rb +64 -0
  21. data/lib/rble/bluez/dbus_session.rb +344 -0
  22. data/lib/rble/bluez/device.rb +86 -0
  23. data/lib/rble/bluez/event_loop.rb +153 -0
  24. data/lib/rble/bluez/gatt_operation_queue.rb +129 -0
  25. data/lib/rble/bluez/pairing_agent.rb +132 -0
  26. data/lib/rble/bluez/pairing_session.rb +212 -0
  27. data/lib/rble/bluez/retry_policy.rb +55 -0
  28. data/lib/rble/bluez.rb +33 -0
  29. data/lib/rble/characteristic.rb +237 -0
  30. data/lib/rble/cli/adapter.rb +88 -0
  31. data/lib/rble/cli/characteristic_helpers.rb +154 -0
  32. data/lib/rble/cli/doctor.rb +309 -0
  33. data/lib/rble/cli/formatters/json.rb +122 -0
  34. data/lib/rble/cli/formatters/text.rb +157 -0
  35. data/lib/rble/cli/hex_dump.rb +48 -0
  36. data/lib/rble/cli/monitor.rb +129 -0
  37. data/lib/rble/cli/pair.rb +103 -0
  38. data/lib/rble/cli/paired.rb +22 -0
  39. data/lib/rble/cli/read.rb +55 -0
  40. data/lib/rble/cli/scan.rb +88 -0
  41. data/lib/rble/cli/show.rb +109 -0
  42. data/lib/rble/cli/status.rb +25 -0
  43. data/lib/rble/cli/unpair.rb +39 -0
  44. data/lib/rble/cli/value_parser.rb +211 -0
  45. data/lib/rble/cli/write.rb +196 -0
  46. data/lib/rble/cli.rb +152 -0
  47. data/lib/rble/company_ids.rb +90 -0
  48. data/lib/rble/connection.rb +539 -0
  49. data/lib/rble/device.rb +54 -0
  50. data/lib/rble/errors.rb +317 -0
  51. data/lib/rble/gatt/uuid_database.rb +395 -0
  52. data/lib/rble/scanner.rb +219 -0
  53. data/lib/rble/service.rb +41 -0
  54. data/lib/rble/tasks/check.rake +154 -0
  55. data/lib/rble/tasks/integration.rake +242 -0
  56. data/lib/rble/tasks.rb +8 -0
  57. data/lib/rble/version.rb +5 -0
  58. data/lib/rble.rb +62 -0
  59. metadata +120 -0
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBLE
4
+ module BlueZ
5
+ # Per-connection queue for serializing GATT operations.
6
+ #
7
+ # BlueZ can behave unpredictably when multiple GATT operations (read, write,
8
+ # start_notify) are executed concurrently on the same connection. This queue
9
+ # ensures operations are executed sequentially with a small delay between them.
10
+ #
11
+ # @example
12
+ # queue = GattOperationQueue.new
13
+ # queue.enqueue { session.async_read_value(char_path) }
14
+ # queue.enqueue { session.async_write_value(char_path, [0x01]) }
15
+ # queue.stop # Call on disconnect
16
+ #
17
+ class GattOperationQueue
18
+ # Delay between GATT operations to prevent BlueZ contention
19
+ INTER_OPERATION_DELAY = 0.05
20
+
21
+ def initialize
22
+ @queue = Thread::Queue.new
23
+ @worker_thread = nil
24
+ @running = false
25
+ @mutex = Mutex.new
26
+ @active_result_queue = nil
27
+ end
28
+
29
+ # Enqueue a GATT operation for sequential execution
30
+ #
31
+ # @yield Block containing the GATT operation to execute
32
+ # @return [Object] Result from the block execution
33
+ # @raise [RuntimeError] if queue is stopped
34
+ # @raise Re-raises any exception from the block
35
+ def enqueue(&block)
36
+ raise 'GattOperationQueue is stopped' unless running?
37
+
38
+ result_queue = Thread::Queue.new
39
+ @queue.push([block, result_queue])
40
+
41
+ # Wait for result (blocking)
42
+ result, error = result_queue.pop
43
+ raise error if error
44
+
45
+ result
46
+ end
47
+
48
+ # Start the worker thread if not already running
49
+ # @return [void]
50
+ def start
51
+ @mutex.synchronize do
52
+ return if @running
53
+
54
+ @running = true
55
+ @worker_thread = Thread.new { worker_loop }
56
+ @worker_thread.name = 'rble-gatt-queue'
57
+ end
58
+ end
59
+
60
+ # Stop the worker thread and clear pending operations
61
+ # @return [void]
62
+ def stop
63
+ @mutex.synchronize do
64
+ return unless @running
65
+
66
+ @running = false
67
+ @queue.push(:shutdown)
68
+ end
69
+
70
+ # Wait for worker thread to finish
71
+ @worker_thread&.join(2)
72
+ @worker_thread&.kill if @worker_thread&.alive?
73
+ @worker_thread = nil
74
+
75
+ # Unblock caller of the currently-executing operation (if any)
76
+ active_rq = @active_result_queue
77
+ @active_result_queue = nil
78
+ active_rq&.push([nil, RuntimeError.new('GATT queue stopped')])
79
+
80
+ # Drain remaining operations and unblock waiting callers
81
+ drain_pending_operations
82
+ end
83
+
84
+ # Check if queue is running
85
+ # @return [Boolean]
86
+ def running?
87
+ @mutex.synchronize { @running }
88
+ end
89
+
90
+ private
91
+
92
+ # Drain pending operations, notifying callers with an error
93
+ # @return [void]
94
+ def drain_pending_operations
95
+ while (item = @queue.pop(true) rescue nil)
96
+ next if item == :shutdown
97
+
98
+ _block, result_queue = item
99
+ result_queue.push([nil, RuntimeError.new('GATT queue stopped')])
100
+ end
101
+ end
102
+
103
+ def worker_loop
104
+ RBLE.logger&.debug('[RBLE] GATT operation queue started')
105
+
106
+ while @running
107
+ item = @queue.pop
108
+ break if item == :shutdown
109
+
110
+ block, result_queue = item
111
+ @active_result_queue = result_queue
112
+ begin
113
+ result = block.call
114
+ result_queue.push([result, nil])
115
+ rescue StandardError => e
116
+ result_queue.push([nil, e])
117
+ ensure
118
+ @active_result_queue = nil
119
+ end
120
+
121
+ # Inter-operation delay to prevent BlueZ contention
122
+ sleep(INTER_OPERATION_DELAY) if @running
123
+ end
124
+
125
+ RBLE.logger&.debug('[RBLE] GATT operation queue stopped')
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dbus'
4
+
5
+ module RBLE
6
+ module BlueZ
7
+ # D-Bus object implementing the org.bluez.Agent1 interface for BLE pairing.
8
+ #
9
+ # BlueZ invokes Agent1 methods during pairing to handle security prompts
10
+ # (PIN entry, passkey confirmation, authorization). This class exports those
11
+ # methods on the system bus so BlueZ can call back into the Ruby process.
12
+ #
13
+ # @example
14
+ # agent = PairingAgent.new(io_handler: handler, force: false)
15
+ # bus.object_server.export(agent)
16
+ #
17
+ class PairingAgent < DBus::Object
18
+ AGENT_INTERFACE = 'org.bluez.Agent1'
19
+ AGENT_PATH = '/org/rble/agent'
20
+
21
+ # @param io_handler [#display, #prompt, #confirm] IO handler for user interaction
22
+ # @param force [Boolean] Skip interactive prompts (auto-accept)
23
+ def initialize(io_handler:, force: false)
24
+ super(AGENT_PATH)
25
+ @io_handler = io_handler
26
+ @force = force
27
+ end
28
+
29
+ dbus_interface AGENT_INTERFACE do
30
+ # Called when the agent is unregistered
31
+ dbus_method :Release do
32
+ # no-op
33
+ end
34
+
35
+ # Request a PIN code from the user
36
+ # @param device [String] D-Bus object path of the device
37
+ # @return [String] PIN code
38
+ dbus_method :RequestPinCode, 'in device:o, out pincode:s' do |device|
39
+ if @force
40
+ @io_handler.display("Auto-accepting PIN for #{device_label(device)}")
41
+ ['0000']
42
+ else
43
+ pin = @io_handler.prompt("Enter PIN for #{device_label(device)}: ")
44
+ [pin.to_s.strip]
45
+ end
46
+ end
47
+
48
+ # Display a PIN code to the user
49
+ # @param device [String] D-Bus object path of the device
50
+ # @param pincode [String] PIN code to display
51
+ dbus_method :DisplayPinCode, 'in device:o, in pincode:s' do |device, pincode|
52
+ @io_handler.display("PIN for #{device_label(device)}: #{pincode}")
53
+ end
54
+
55
+ # Request a passkey (numeric) from the user
56
+ # @param device [String] D-Bus object path of the device
57
+ # @return [Integer] Passkey (0-999999)
58
+ dbus_method :RequestPasskey, 'in device:o, out passkey:u' do |device|
59
+ if @force
60
+ @io_handler.display("Auto-accepting passkey for #{device_label(device)}")
61
+ [0]
62
+ else
63
+ input = @io_handler.prompt("Enter passkey (0-999999) for #{device_label(device)}: ")
64
+ passkey = input.to_s.strip.to_i
65
+ passkey = passkey.clamp(0, 999_999)
66
+ [passkey]
67
+ end
68
+ end
69
+
70
+ # Display a passkey with the number of digits entered so far
71
+ # @param device [String] D-Bus object path of the device
72
+ # @param passkey [Integer] Passkey being entered
73
+ # @param entered [Integer] Number of digits entered
74
+ dbus_method :DisplayPasskey, 'in device:o, in passkey:u, in entered:q' do |device, passkey, entered|
75
+ @io_handler.display("Passkey for #{device_label(device)}: #{format('%06d', passkey)} (#{entered} digits entered)")
76
+ end
77
+
78
+ # Request confirmation of a passkey
79
+ # @param device [String] D-Bus object path of the device
80
+ # @param passkey [Integer] Passkey to confirm
81
+ dbus_method :RequestConfirmation, 'in device:o, in passkey:u' do |device, passkey|
82
+ if @force
83
+ @io_handler.display("Auto-confirming passkey #{format('%06d', passkey)} for #{device_label(device)}")
84
+ else
85
+ accepted = @io_handler.confirm("Confirm passkey #{format('%06d', passkey)} for #{device_label(device)}? [Y/n] ")
86
+ unless accepted
87
+ raise DBus.error('org.bluez.Error.Rejected'), "Passkey rejected by user for #{device_label(device)}"
88
+ end
89
+ end
90
+ end
91
+
92
+ # Request authorization for the device
93
+ # @param device [String] D-Bus object path of the device
94
+ dbus_method :RequestAuthorization, 'in device:o' do |device|
95
+ if @force
96
+ @io_handler.display("Auto-authorizing #{device_label(device)}")
97
+ else
98
+ accepted = @io_handler.confirm("Authorize #{device_label(device)}? [Y/n] ")
99
+ unless accepted
100
+ raise DBus.error('org.bluez.Error.Rejected'), "Authorization rejected by user for #{device_label(device)}"
101
+ end
102
+ end
103
+ end
104
+
105
+ # Authorize a service (auto-authorize, no prompt needed)
106
+ # @param device [String] D-Bus object path of the device
107
+ # @param uuid [String] Service UUID
108
+ dbus_method :AuthorizeService, 'in device:o, in uuid:s' do |device, uuid|
109
+ @io_handler.display("Authorizing service #{uuid} for #{device_label(device)}")
110
+ end
111
+
112
+ # Called when a pairing request is cancelled
113
+ dbus_method :Cancel do
114
+ @io_handler.display('Pairing cancelled')
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ # Extract a human-readable label from a D-Bus device path
121
+ # @param device_path [String] D-Bus object path like /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF
122
+ # @return [String] MAC address or the raw path
123
+ def device_label(device_path)
124
+ if device_path =~ /dev_([0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2})/i
125
+ ::Regexp.last_match(1).tr('_', ':')
126
+ else
127
+ device_path
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dbus'
4
+
5
+ module RBLE
6
+ module BlueZ
7
+ # Manages the lifecycle of a PairingAgent during pair/unpair operations.
8
+ #
9
+ # Each PairingSession creates a dedicated D-Bus bus connection, exports a
10
+ # PairingAgent on it, registers the agent with BlueZ's AgentManager1, and
11
+ # drives the Pair() call with a DBus::Main event loop so agent callbacks
12
+ # are processed in the same thread context.
13
+ #
14
+ # The agent MUST be on the SAME bus as RegisterAgent and Pair() calls.
15
+ # A dedicated bus per session avoids conflicts with the shared DBusSession.
16
+ #
17
+ # @example
18
+ # session = PairingSession.new(io_handler: handler, force: false, capability: "KeyboardDisplay")
19
+ # result = session.pair("/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF", timeout: 30)
20
+ # # => :paired or :already_paired
21
+ #
22
+ class PairingSession
23
+ CAPABILITY_MAP = {
24
+ 'low' => 'NoInputNoOutput',
25
+ 'medium' => 'DisplayYesNo',
26
+ 'high' => 'KeyboardDisplay',
27
+ nil => 'KeyboardDisplay'
28
+ }.freeze
29
+
30
+ # @param io_handler [#display, #prompt, #confirm] IO handler for user interaction
31
+ # @param force [Boolean] Skip interactive prompts (auto-accept)
32
+ # @param capability [String] Agent capability from CAPABILITY_MAP values
33
+ def initialize(io_handler:, force: false, capability: 'KeyboardDisplay')
34
+ @io_handler = io_handler
35
+ @force = force
36
+ @capability = capability
37
+ @bus = nil
38
+ @agent = nil
39
+ @main_loop = nil
40
+ end
41
+
42
+ # Pair with a device by exporting an agent and calling Device1.Pair()
43
+ #
44
+ # Creates a dedicated D-Bus bus, exports the PairingAgent, registers it
45
+ # with AgentManager1, then calls Pair() with an event loop to process
46
+ # agent callbacks.
47
+ #
48
+ # @param device_path [String] D-Bus device path (e.g., /org/bluez/hci0/dev_AA_BB_...)
49
+ # @param timeout [Numeric] Timeout in seconds for the pairing operation
50
+ # @return [Symbol] :paired on success, :already_paired if already paired
51
+ # @raise [RBLE::AuthenticationError] if authentication fails
52
+ # @raise [RBLE::ConnectionFailed] if connection to device fails
53
+ # @raise [RBLE::TimeoutError] if pairing times out
54
+ # @raise [RBLE::Error] on other failures
55
+ def pair(device_path, timeout: 30)
56
+ setup_bus_and_agent
57
+ register_agent
58
+ call_pair(device_path, timeout: timeout)
59
+ ensure
60
+ cleanup
61
+ end
62
+
63
+ private
64
+
65
+ # Create dedicated D-Bus bus and export the agent
66
+ def setup_bus_and_agent
67
+ @bus = DBus::ASystemBus.new
68
+ @agent = PairingAgent.new(io_handler: @io_handler, force: @force)
69
+ @bus.object_server.export(@agent)
70
+ end
71
+
72
+ # Register the agent with BlueZ's AgentManager1
73
+ def register_agent
74
+ bluez = @bus.service(BLUEZ_SERVICE)
75
+ bluez_root = bluez.object('/')
76
+ bluez_root.introspect
77
+
78
+ agent_manager = bluez_root[AGENT_MANAGER_INTERFACE]
79
+ agent_manager.RegisterAgent(PairingAgent::AGENT_PATH, @capability)
80
+ agent_manager.RequestDefaultAgent(PairingAgent::AGENT_PATH)
81
+ end
82
+
83
+ # Call Device1.Pair() with an event loop for agent callback processing
84
+ #
85
+ # Uses a Thread::Queue to communicate the result from the async callback
86
+ # back to the calling thread. DBus::Main runs in a background thread to
87
+ # process agent method invocations from BlueZ.
88
+ #
89
+ # @param device_path [String] D-Bus device path
90
+ # @param timeout [Numeric] Timeout in seconds
91
+ # @return [Symbol] :paired or :already_paired
92
+ def call_pair(device_path, timeout: 30)
93
+ result_queue = Thread::Queue.new
94
+
95
+ # Introspect device to get Device1 interface
96
+ device_obj = @bus.service(BLUEZ_SERVICE).object(device_path)
97
+ device_obj.introspect
98
+ device_iface = device_obj[DEVICE_INTERFACE]
99
+
100
+ # Start event loop in background thread for agent callbacks
101
+ @main_loop = DBus::Main.new
102
+ @main_loop << @bus
103
+
104
+ loop_thread = Thread.new do
105
+ @main_loop.run
106
+ rescue StandardError => e
107
+ RBLE.logger&.debug("[RBLE] PairingSession event loop error: #{e.message}")
108
+ end
109
+
110
+ # Call Pair() asynchronously - result delivered via callback
111
+ device_iface.Pair do |reply|
112
+ if reply.is_a?(DBus::Error)
113
+ result_queue.push([:error, reply])
114
+ else
115
+ result_queue.push([:success, nil])
116
+ end
117
+ end
118
+
119
+ # Wait for result with timeout
120
+ result = result_queue.pop(timeout: timeout)
121
+
122
+ # Stop the event loop
123
+ @main_loop&.quit
124
+ loop_thread&.join(2)
125
+
126
+ if result.nil?
127
+ raise RBLE::TimeoutError.new('Pair', timeout)
128
+ end
129
+
130
+ status, error = result
131
+
132
+ if status == :error
133
+ translate_pair_error(error, device_path)
134
+ else
135
+ :paired
136
+ end
137
+ end
138
+
139
+ # Translate D-Bus errors from Pair() into RBLE exceptions
140
+ #
141
+ # @param error [DBus::Error] The D-Bus error
142
+ # @param device_path [String] Device path for error context
143
+ # @return [Symbol] :already_paired if device is already paired
144
+ # @raise [RBLE::AuthenticationError] on auth failures
145
+ # @raise [RBLE::ConnectionFailed] on connection failures
146
+ # @raise [RBLE::Error] on other failures
147
+ def translate_pair_error(error, device_path)
148
+ address = extract_address(device_path)
149
+
150
+ case error.name
151
+ when 'org.bluez.Error.AlreadyExists'
152
+ :already_paired
153
+ when 'org.bluez.Error.AuthenticationFailed',
154
+ 'org.bluez.Error.AuthenticationCanceled',
155
+ 'org.bluez.Error.AuthenticationRejected',
156
+ 'org.bluez.Error.AuthenticationTimeout'
157
+ raise RBLE::AuthenticationError.new(address)
158
+ when 'org.bluez.Error.ConnectionAttemptFailed'
159
+ raise RBLE::ConnectionFailed.new(address, 'device not reachable (out of range?)')
160
+ else
161
+ raise RBLE::Error, "Pairing failed for #{address}: #{error.message}"
162
+ end
163
+ end
164
+
165
+ # Extract MAC address from D-Bus device path
166
+ # @param device_path [String] D-Bus path like /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF
167
+ # @return [String] MAC address or 'unknown'
168
+ def extract_address(device_path)
169
+ if device_path =~ /dev_([0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2})/i
170
+ ::Regexp.last_match(1).tr('_', ':')
171
+ else
172
+ 'unknown'
173
+ end
174
+ end
175
+
176
+ # Clean up D-Bus resources
177
+ # Unregisters agent, unexports it, and closes the bus connection.
178
+ # All errors during cleanup are silently ignored.
179
+ def cleanup
180
+ # Stop event loop if still running
181
+ @main_loop&.quit rescue nil
182
+
183
+ # Unregister agent from AgentManager1
184
+ if @bus
185
+ begin
186
+ bluez = @bus.service(BLUEZ_SERVICE)
187
+ bluez_root = bluez.object('/')
188
+ bluez_root.introspect
189
+ agent_manager = bluez_root[AGENT_MANAGER_INTERFACE]
190
+ agent_manager.UnregisterAgent(PairingAgent::AGENT_PATH)
191
+ rescue StandardError
192
+ # Ignore errors during cleanup
193
+ end
194
+ end
195
+
196
+ # Unexport agent from bus
197
+ if @bus && @agent
198
+ begin
199
+ @bus.object_server.unexport(@agent)
200
+ rescue StandardError
201
+ # Ignore errors during cleanup
202
+ end
203
+ end
204
+
205
+ # Close the dedicated bus
206
+ @bus = nil
207
+ @agent = nil
208
+ @main_loop = nil
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBLE
4
+ module BlueZ
5
+ # Retry policy for handling transient BlueZ errors with exponential backoff.
6
+ #
7
+ # BlueZ can return org.bluez.Error.InProgress when operations are attempted
8
+ # too quickly or when the stack is busy. This module provides retry logic
9
+ # with exponential backoff to handle these cases gracefully.
10
+ #
11
+ # @example
12
+ # include RetryPolicy
13
+ #
14
+ # with_inprogress_retry do
15
+ # session.async_read_value(char_path)
16
+ # end
17
+ #
18
+ module RetryPolicy
19
+ # Maximum number of retry attempts for InProgress errors
20
+ MAX_RETRIES = 3
21
+
22
+ # Initial delay before first retry (50ms)
23
+ INITIAL_DELAY = 0.05
24
+
25
+ # Maximum delay between retries (500ms)
26
+ MAX_DELAY = 0.5
27
+
28
+ # Execute a block with retry logic for InProgress errors
29
+ #
30
+ # @yield Block containing the operation to execute
31
+ # @return [Object] Result from the block
32
+ # @raise [OperationInProgressError] if all retries exhausted
33
+ # @raise Re-raises any other exception
34
+ def with_inprogress_retry
35
+ attempts = 0
36
+ delay = INITIAL_DELAY
37
+
38
+ begin
39
+ yield
40
+ rescue DBus::Error => e
41
+ if e.name == 'org.bluez.Error.InProgress' && attempts < MAX_RETRIES
42
+ attempts += 1
43
+ RBLE.logger&.debug("[RBLE] InProgress error, retry #{attempts}/#{MAX_RETRIES} after #{(delay * 1000).round}ms")
44
+ sleep(delay)
45
+ delay = [delay * 2, MAX_DELAY].min # Exponential backoff with cap
46
+ retry
47
+ end
48
+
49
+ # Re-raise if not InProgress or retries exhausted
50
+ raise
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/rble/bluez.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'dbus'
5
+ rescue LoadError
6
+ raise LoadError,
7
+ "The ruby-dbus gem is required for the BlueZ backend on Linux. " \
8
+ "Install it with: gem install ruby-dbus"
9
+ end
10
+
11
+ module RBLE
12
+ module BlueZ
13
+ BLUEZ_SERVICE = 'org.bluez'
14
+ ADAPTER_INTERFACE = 'org.bluez.Adapter1'
15
+ DEVICE_INTERFACE = 'org.bluez.Device1'
16
+ GATT_SERVICE_INTERFACE = 'org.bluez.GattService1'
17
+ GATT_CHARACTERISTIC_INTERFACE = 'org.bluez.GattCharacteristic1'
18
+ GATT_DESCRIPTOR_INTERFACE = 'org.bluez.GattDescriptor1'
19
+ PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
20
+ OBJECT_MANAGER_INTERFACE = 'org.freedesktop.DBus.ObjectManager'
21
+ AGENT_MANAGER_INTERFACE = 'org.bluez.AgentManager1'
22
+ end
23
+ end
24
+
25
+ require_relative 'bluez/dbus_connection'
26
+ require_relative 'bluez/adapter'
27
+ require_relative 'bluez/event_loop'
28
+ require_relative 'bluez/dbus_session'
29
+ require_relative 'bluez/device'
30
+ require_relative 'bluez/gatt_operation_queue'
31
+ require_relative 'bluez/retry_policy'
32
+ require_relative 'bluez/pairing_agent'
33
+ require_relative 'bluez/pairing_session'