sb-ble 0.5.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.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.semver +5 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +130 -0
- data/LICENSE +21 -0
- data/README.org +62 -0
- data/Rakefile +51 -0
- data/bin/rble +0 -0
- data/lib/ble.rb +112 -0
- data/lib/ble/adapter.rb +162 -0
- data/lib/ble/agent.rb +46 -0
- data/lib/ble/char_desc.rb +63 -0
- data/lib/ble/char_registry.rb +142 -0
- data/lib/ble/characteristic.rb +85 -0
- data/lib/ble/db_eddystone.rb +50 -0
- data/lib/ble/db_nordic.rb +26 -0
- data/lib/ble/db_sig_characteristic.rb +142 -0
- data/lib/ble/db_sig_service.rb +59 -0
- data/lib/ble/device.rb +494 -0
- data/lib/ble/notifications.rb +44 -0
- data/lib/ble/service.rb +121 -0
- data/lib/ble/uuid.rb +25 -0
- data/lib/ble/version.rb +15 -0
- data/sb-ble.gemspec +112 -0
- data/spec/ruby-ble_spec.rb +7 -0
- data/spec/spec_helper.rb +29 -0
- metadata +296 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module BLE
|
2
|
+
module Service
|
3
|
+
add 0x1800,
|
4
|
+
name: 'Generic Access',
|
5
|
+
type: 'org.bluetooth.service.generic_access'
|
6
|
+
|
7
|
+
add 0x1801,
|
8
|
+
name: 'Generic Attribute',
|
9
|
+
type: 'org.bluetooth.service.generic_attribute'
|
10
|
+
|
11
|
+
add 0x1802,
|
12
|
+
name: 'Immediate Alert',
|
13
|
+
type: 'org.bluetooth.service.immediate_alert'
|
14
|
+
|
15
|
+
add 0x1803,
|
16
|
+
name: 'Link Loss',
|
17
|
+
type: 'org.bluetooth.service.link_loss'
|
18
|
+
|
19
|
+
add 0x1804,
|
20
|
+
name: 'Tx Power',
|
21
|
+
type: 'org.bluetooth.service.tx_power'
|
22
|
+
|
23
|
+
add 0x1805,
|
24
|
+
name: 'Current Time Service',
|
25
|
+
type: 'org.bluetooth.service.current_time'
|
26
|
+
|
27
|
+
add 0x180A,
|
28
|
+
name: 'Device Information',
|
29
|
+
type: 'org.bluetooth.service.device_information'
|
30
|
+
|
31
|
+
add 0x180F,
|
32
|
+
name: 'Battery Service',
|
33
|
+
type: 'org.bluetooth.service.battery_service'
|
34
|
+
|
35
|
+
add 0x1811,
|
36
|
+
name: 'Alert Notification Service',
|
37
|
+
type: 'org.bluetooth.service.alert_notification'
|
38
|
+
|
39
|
+
add 0x1812,
|
40
|
+
name: 'Human Interface Device',
|
41
|
+
type: 'org.bluetooth.service.human_interface_device'
|
42
|
+
|
43
|
+
add 0x1819,
|
44
|
+
name: 'Location and Navigation',
|
45
|
+
type: 'org.bluetooth.service.location_and_navigation'
|
46
|
+
|
47
|
+
add 0x181A,
|
48
|
+
name: 'Environmental Sensing',
|
49
|
+
type: 'org.bluetooth.service.environmental_sensing'
|
50
|
+
|
51
|
+
add 0x181C,
|
52
|
+
name: 'User Data',
|
53
|
+
type: 'org.bluetooth.service.user_data'
|
54
|
+
|
55
|
+
add 0x181D,
|
56
|
+
name: 'Weight Scale',
|
57
|
+
type: 'org.bluetooth.service.weight_scale'
|
58
|
+
end
|
59
|
+
end
|
data/lib/ble/device.rb
ADDED
@@ -0,0 +1,494 @@
|
|
1
|
+
module BLE
|
2
|
+
# Create de Device object
|
3
|
+
# d = Device::new('hci0', 'aa:bb:dd:dd:ee:ff')
|
4
|
+
# d = Adapter.new('hci0')['aa:bb:dd:dd:ee:ff']
|
5
|
+
#
|
6
|
+
# d.services
|
7
|
+
# d.characteristics(:environmental_sensing)
|
8
|
+
# d[:environmental_sensing, :temperature]
|
9
|
+
#
|
10
|
+
class Device
|
11
|
+
include Notifications
|
12
|
+
# Notify that you need to have the device in a connected state
|
13
|
+
class NotConnected < Error ; end
|
14
|
+
|
15
|
+
# @param adapter [String] adapter unix device name
|
16
|
+
# @param dev [String] device MAC address
|
17
|
+
# @param auto_refresh [Boolean] gather information about device
|
18
|
+
# on connection
|
19
|
+
def initialize(adapter, dev, auto_refresh: true)
|
20
|
+
@adapter, @dev = adapter, dev
|
21
|
+
@auto_refresh = auto_refresh
|
22
|
+
@services = {}
|
23
|
+
|
24
|
+
@n_adapter = adapter
|
25
|
+
@p_adapter = "/org/bluez/#{@n_adapter}"
|
26
|
+
@o_adapter = BLUEZ.object(@p_adapter)
|
27
|
+
@o_adapter.introspect
|
28
|
+
|
29
|
+
@n_dev = 'dev_' + dev.tr(':', '_')
|
30
|
+
@p_dev = "/org/bluez/#{@n_adapter}/#{@n_dev}"
|
31
|
+
@o_dev = BLUEZ.object(@p_dev)
|
32
|
+
@o_dev.introspect
|
33
|
+
|
34
|
+
self.refresh if @auto_refresh
|
35
|
+
|
36
|
+
@o_dev[I_PROPERTIES]
|
37
|
+
.on_signal('PropertiesChanged') do |intf, props|
|
38
|
+
case intf
|
39
|
+
when I_DEVICE
|
40
|
+
case props['Connected']
|
41
|
+
when true
|
42
|
+
self.refresh if @auto_refresh
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# This removes the remote device object.
|
49
|
+
# It will remove also the pairing information.
|
50
|
+
# @return [Boolean]
|
51
|
+
def remove
|
52
|
+
@o_adapter[I_ADAPTER].RemoveDevice(@p_dev)
|
53
|
+
true
|
54
|
+
rescue DBus::Error => e
|
55
|
+
case e.name
|
56
|
+
when E_FAILED then false
|
57
|
+
when E_DOES_NOT_EXIST then raise StalledObject
|
58
|
+
when E_UNKNOWN_OBJECT then raise StalledObject
|
59
|
+
else raise ScriptError
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# This method will connect to the remote device,
|
65
|
+
# initiate pairing and then retrieve all SDP records
|
66
|
+
# (or GATT primary services).
|
67
|
+
# If the application has registered its own agent,
|
68
|
+
# then that specific agent will be used. Otherwise
|
69
|
+
# it will use the default agent.
|
70
|
+
# Only for applications like a pairing wizard it
|
71
|
+
# would make sense to have its own agent. In almost
|
72
|
+
# all other cases the default agent will handle this just fine.
|
73
|
+
# In case there is no application agent and also
|
74
|
+
# no default agent present, this method will fail.
|
75
|
+
#
|
76
|
+
# @return [Boolean]
|
77
|
+
def pair
|
78
|
+
@o_dev[I_DEVICE].Pair
|
79
|
+
true
|
80
|
+
rescue DBus::Error => e
|
81
|
+
case e.name
|
82
|
+
when E_INVALID_ARGUMENTS then false
|
83
|
+
when E_FAILED then false
|
84
|
+
when E_ALREADY_EXISTS then true
|
85
|
+
when E_AUTH_CANCELED then raise NotAuthorized
|
86
|
+
when E_AUTH_FAILED then raise NotAuthorized
|
87
|
+
when E_AUTH_REJECTED then raise NotAuthorized
|
88
|
+
when E_AUTH_TIMEOUT then raise NotAuthorized
|
89
|
+
when E_AUTH_ATTEMPT_FAILED then raise NotAuthorized
|
90
|
+
else raise ScriptError
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# This method can be used to cancel a pairing
|
95
|
+
# operation initiated by the Pair method.
|
96
|
+
# @return [Boolean]
|
97
|
+
def cancel_pairing
|
98
|
+
@o_dev[I_DEVICE].CancelPairing
|
99
|
+
true
|
100
|
+
rescue DBus::Error => e
|
101
|
+
case e.name
|
102
|
+
when E_DOES_NOT_EXIST then true
|
103
|
+
when E_FAILED then false
|
104
|
+
else raise ScriptError
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# This connect to the specified profile UUID or to any (:all)
|
109
|
+
# profiles the remote device supports that can be connected to
|
110
|
+
# and have been flagged as auto-connectable on our side. If
|
111
|
+
# only subset of profiles is already connected it will try to
|
112
|
+
# connect currently disconnected ones. If at least one
|
113
|
+
# profile was connected successfully this method will indicate
|
114
|
+
# success.
|
115
|
+
# @return [Boolean]
|
116
|
+
def connect(profile=:all)
|
117
|
+
case profile
|
118
|
+
when UUID::REGEX
|
119
|
+
@o_dev[I_DEVICE].ConnectProfile(profile)
|
120
|
+
when :all
|
121
|
+
@o_dev[I_DEVICE].Connect()
|
122
|
+
else raise ArgumentError, "profile uuid or :all expected"
|
123
|
+
end
|
124
|
+
true
|
125
|
+
rescue DBus::Error => e
|
126
|
+
case e.name
|
127
|
+
when E_NOT_READY
|
128
|
+
when E_FAILED
|
129
|
+
when E_IN_PROGRESS
|
130
|
+
false
|
131
|
+
when E_ALREADY_CONNECTED
|
132
|
+
true
|
133
|
+
when E_UNKNOWN_OBJECT
|
134
|
+
raise StalledObject
|
135
|
+
else raise ScriptError
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# This method gracefully disconnects :all connected profiles
|
140
|
+
# and then terminates low-level ACL connection.
|
141
|
+
# ACL connection will be terminated even if some profiles
|
142
|
+
# were not disconnected properly e.g. due to misbehaving device.
|
143
|
+
# This method can be also used to cancel a preceding #connect
|
144
|
+
# call before a reply to it has been received.
|
145
|
+
# If a profile UUID is specified, only this profile is disconnected,
|
146
|
+
# and as their is no connection tracking for a profile, so
|
147
|
+
# as long as the profile is registered this will always succeed
|
148
|
+
# @return [Boolean]
|
149
|
+
def disconnect(profile=:all)
|
150
|
+
case profile
|
151
|
+
when UUID::REGEX
|
152
|
+
@o_dev[I_DEVICE].DisconnectProfile(profile)
|
153
|
+
when :all
|
154
|
+
@o_dev[I_DEVICE].Disconnect()
|
155
|
+
else raise ArgumentError, "profile uuid or :all expected"
|
156
|
+
end
|
157
|
+
true
|
158
|
+
rescue DBus::Error => e
|
159
|
+
case e.name
|
160
|
+
when E_FAILED
|
161
|
+
when E_IN_PROGRESS
|
162
|
+
false
|
163
|
+
when E_INVALID_ARGUMENTS
|
164
|
+
raise ArgumentError, "unsupported profile (#{profile})"
|
165
|
+
when E_NOT_SUPPORTED
|
166
|
+
raise NotSupported
|
167
|
+
when E_NOT_CONNECTED
|
168
|
+
true
|
169
|
+
when E_UNKNOWN_OBJECT
|
170
|
+
raise StalledObject
|
171
|
+
else raise ScriptError
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Indicates if the remote device is paired
|
176
|
+
def is_paired?
|
177
|
+
@o_dev[I_DEVICE]['Paired']
|
178
|
+
rescue DBus::Error => e
|
179
|
+
case e.name
|
180
|
+
when E_UNKNOWN_OBJECT
|
181
|
+
raise StalledObject
|
182
|
+
else raise ScriptError
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Indicates if the remote device is currently connected.
|
187
|
+
def is_connected?
|
188
|
+
@o_dev[I_DEVICE]['Connected']
|
189
|
+
rescue DBus::Error => e
|
190
|
+
case e.name
|
191
|
+
when E_UNKNOWN_OBJECT
|
192
|
+
raise StalledObject
|
193
|
+
else raise ScriptError
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# List of available services as UUID.
|
198
|
+
#
|
199
|
+
# @raise [NotConnected] if device is not in a connected state
|
200
|
+
# @note The list is retrieve once when object is
|
201
|
+
# connected if auto_refresh is enable, otherwise
|
202
|
+
# you need to call {#refresh}.
|
203
|
+
# @note This is the list of UUIDs for which we have an entry
|
204
|
+
# in the underlying api (bluez-dbus), which can be less
|
205
|
+
# that the list of advertised UUIDs.
|
206
|
+
# @example list available services
|
207
|
+
# $d.services.each {|uuid|
|
208
|
+
# info = BLE::Service[uuid]
|
209
|
+
# name = info.nil? ? uuid : info[:name]
|
210
|
+
# puts name
|
211
|
+
# }
|
212
|
+
#
|
213
|
+
# @return [Array<String>] List of service UUID
|
214
|
+
def services
|
215
|
+
_require_connection!
|
216
|
+
@services.keys
|
217
|
+
end
|
218
|
+
|
219
|
+
# Check if service is available on the device
|
220
|
+
# @return [Boolean]
|
221
|
+
def has_service?(service)
|
222
|
+
@service.key?(_uuid_service(service))
|
223
|
+
end
|
224
|
+
|
225
|
+
# List of available characteristics UUID for a service.
|
226
|
+
#
|
227
|
+
# @param service service can be a UUID, a service type or
|
228
|
+
# a service nickname
|
229
|
+
# @return [Array<String>, nil] list of characteristics or +nil+ if the
|
230
|
+
# service doesn't exist
|
231
|
+
# @raise [NotConnected] if device is not in a connected state
|
232
|
+
# @note The list is retrieve once when object is
|
233
|
+
# connected if auto_refresh is enable, otherwise
|
234
|
+
# you need to call {#refresh}.
|
235
|
+
def characteristics(service)
|
236
|
+
_require_connection!
|
237
|
+
if chars = _characteristics(service)
|
238
|
+
chars.keys
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# The Bluetooth device address of the remote device.
|
243
|
+
# @return [String] MAC address
|
244
|
+
def address
|
245
|
+
@o_dev[I_DEVICE]['Address']
|
246
|
+
end
|
247
|
+
|
248
|
+
# The Bluetooth remote name.
|
249
|
+
# It is better to always use the {#alias} when displaying the
|
250
|
+
# devices name.
|
251
|
+
# @return [String] name
|
252
|
+
def name # optional
|
253
|
+
@o_dev[I_DEVICE]['Name']
|
254
|
+
end
|
255
|
+
|
256
|
+
# The name alias for the remote device.
|
257
|
+
# The alias can be used to have a different friendly name for the
|
258
|
+
# remote device.
|
259
|
+
# In case no alias is set, it will return the remote device name.
|
260
|
+
# @return [String]
|
261
|
+
def alias
|
262
|
+
@o_dev[I_DEVICE]['Alias']
|
263
|
+
end
|
264
|
+
# Setting an empty string or nil as alias will convert it
|
265
|
+
# back to the remote device name.
|
266
|
+
# @param val [String, nil]
|
267
|
+
# @return [void]
|
268
|
+
def alias=(val)
|
269
|
+
@o_dev[I_DEVICE]['Alias'] = val.nil? ? "" : val.to_str
|
270
|
+
end
|
271
|
+
|
272
|
+
# Is the device trusted?
|
273
|
+
# @return [Boolean]
|
274
|
+
def is_trusted?
|
275
|
+
@o_dev[I_DEVICE]['Trusted']
|
276
|
+
end
|
277
|
+
|
278
|
+
# Indicates if the remote is seen as trusted. This
|
279
|
+
# setting can be changed by the application.
|
280
|
+
# @param val [Boolean]
|
281
|
+
# @return [void]
|
282
|
+
def trusted=(val)
|
283
|
+
if ! [ true, false ].include?(val)
|
284
|
+
raise ArgumentError, "value must be a boolean"
|
285
|
+
end
|
286
|
+
@o_dev[I_DEVICE]['Trusted'] = val
|
287
|
+
end
|
288
|
+
|
289
|
+
# Is the device blocked?
|
290
|
+
# @return [Boolean]
|
291
|
+
def is_blocked?
|
292
|
+
@o_dev[I_DEVICE]['Blocked']
|
293
|
+
end
|
294
|
+
|
295
|
+
# If set to true any incoming connections from the
|
296
|
+
# device will be immediately rejected. Any device
|
297
|
+
# drivers will also be removed and no new ones will
|
298
|
+
# be probed as long as the device is blocked
|
299
|
+
# @param val [Boolean]
|
300
|
+
# @return [void]
|
301
|
+
def blocked=(val)
|
302
|
+
if ! [ true, false ].include?(val)
|
303
|
+
raise ArgumentError, "value must be a boolean"
|
304
|
+
end
|
305
|
+
@o_dev[I_DEVICE]['Blocked'] = val
|
306
|
+
end
|
307
|
+
|
308
|
+
# Received Signal Strength Indicator of the remote
|
309
|
+
# device (inquiry or advertising).
|
310
|
+
# @return [Integer]
|
311
|
+
def rssi # optional
|
312
|
+
@o_dev[I_DEVICE]['RSSI']
|
313
|
+
rescue DBus::Error => e
|
314
|
+
case e.name
|
315
|
+
when E_INVALID_ARGS then raise NotSupported
|
316
|
+
else raise ScriptError
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Advertised transmitted power level (inquiry or advertising).
|
321
|
+
# @return [Integer]
|
322
|
+
def tx_power # optional
|
323
|
+
@o_dev[I_DEVICE]['TxPower']
|
324
|
+
rescue DBus::Error => e
|
325
|
+
case e.name
|
326
|
+
when E_INVALID_ARGS then raise NotSupported
|
327
|
+
else raise ScriptError
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
|
332
|
+
# Refresh list of services and characteristics
|
333
|
+
# @return [Boolean]
|
334
|
+
def refresh
|
335
|
+
refresh!
|
336
|
+
true
|
337
|
+
rescue NotConnected, StalledObject
|
338
|
+
false
|
339
|
+
end
|
340
|
+
|
341
|
+
# Refresh list of services and characteristics
|
342
|
+
# @raise [NotConnected] if device is not in a connected state
|
343
|
+
# @return [self]
|
344
|
+
def refresh!
|
345
|
+
_require_connection!
|
346
|
+
max_wait ||= 1.5 # Use ||= due to the retry
|
347
|
+
@services = Hash[@o_dev[I_DEVICE]['GattServices'].map {|p_srv|
|
348
|
+
o_srv = BLUEZ.object(p_srv)
|
349
|
+
o_srv.introspect
|
350
|
+
srv = o_srv[I_PROPERTIES].GetAll(I_GATT_SERVICE).first
|
351
|
+
char = Hash[srv['Characteristics'].map {|p_char|
|
352
|
+
o_char = BLUEZ.object(p_char)
|
353
|
+
o_char.introspect
|
354
|
+
uuid = o_char[I_GATT_CHARACTERISTIC]['UUID' ].downcase
|
355
|
+
flags = o_char[I_GATT_CHARACTERISTIC]['Flags']
|
356
|
+
[ uuid, Characteristic.new({ :uuid => uuid, :flags => flags, :obj => o_char }) ]
|
357
|
+
}]
|
358
|
+
uuid = srv['UUID'].downcase
|
359
|
+
[ uuid, { :uuid => uuid,
|
360
|
+
:primary => srv['Primary'],
|
361
|
+
:characteristics => char } ]
|
362
|
+
}]
|
363
|
+
self
|
364
|
+
rescue DBus::Error => e
|
365
|
+
case e.name
|
366
|
+
when E_UNKNOWN_OBJECT
|
367
|
+
raise StalledObject
|
368
|
+
when E_INVALID_ARGS
|
369
|
+
# That's probably because all the bluez information
|
370
|
+
# haven't been collected yet on dbus for GattServices
|
371
|
+
if max_wait > 0
|
372
|
+
sleep(0.25) ; max_wait -= 0.25 ; retry
|
373
|
+
end
|
374
|
+
raise NotReady
|
375
|
+
|
376
|
+
else raise ScriptError
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Get value for a service/characteristic.
|
381
|
+
#
|
382
|
+
# @param service [String, Symbol]
|
383
|
+
# @param characteristic [String, Symbol]
|
384
|
+
# @param raw [Boolean] When raw is true the value get is a binary string, instead of an object corresponding to the decoded characteristic (float, integer, array, ...)
|
385
|
+
# @raise [NotConnected] if device is not in a connected state
|
386
|
+
# @raise [NotYetImplemented] encryption is not implemented yet
|
387
|
+
# @raise [Service::NotFound, Characteristic::NotFound] if service/characteristic doesn't exist on this device
|
388
|
+
# @raise [AccessUnavailable] if not available for reading
|
389
|
+
# @return [Object]
|
390
|
+
def [](service, characteristic, raw: false)
|
391
|
+
_require_connection!
|
392
|
+
uuid = _uuid_characteristic(characteristic)
|
393
|
+
chars = _characteristics(service)
|
394
|
+
raise Service::NotFound, service if chars.nil?
|
395
|
+
char = chars[uuid]
|
396
|
+
raise Characteristic::NotFound, characteristic if char.nil?
|
397
|
+
|
398
|
+
if char.flag?('read')
|
399
|
+
char.read(raw: raw)
|
400
|
+
elsif char.flag?('encrypt-read') ||
|
401
|
+
char.flag?('encrypt-authenticated-read')
|
402
|
+
raise NotYetImplemented
|
403
|
+
else
|
404
|
+
raise AccessUnavailable
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# Set value for a service/characteristic
|
409
|
+
#
|
410
|
+
# @param service [String, Symbol]
|
411
|
+
# @param characteristic [String, Symbol]
|
412
|
+
# @param val [Boolean]
|
413
|
+
# @param raw [Boolean] When raw is true the value set is a binary string, instead of an object corresponding to the decoded characteristic (float, integer, array, ...).
|
414
|
+
# @raise [NotConnected] if device is not in a connected state
|
415
|
+
# @raise [NotYetImplemented] encryption is not implemented yet
|
416
|
+
# @raise [Service::NotFound, Characteristic::NotFound] if service/characteristic doesn't exist on this device
|
417
|
+
# @raise [AccessUnavailable] if not available for writing
|
418
|
+
# @return [void]
|
419
|
+
def []=(service, characteristic, val, raw: false)
|
420
|
+
_require_connection!
|
421
|
+
uuid = _uuid_characteristic(characteristic)
|
422
|
+
chars = _characteristics(service)
|
423
|
+
raise ServiceNotFound, service if chars.nil?
|
424
|
+
char = chars[uuid]
|
425
|
+
raise CharacteristicNotFound, characteristic if char.nil?
|
426
|
+
|
427
|
+
if char.flag?('write') ||
|
428
|
+
char.flag?('write-without-response')
|
429
|
+
char.write(val, raw: raw)
|
430
|
+
elsif char.flag?('encrypt-write') ||
|
431
|
+
char.flag?('encrypt-authenticated-write')
|
432
|
+
raise NotYetImplemented
|
433
|
+
else
|
434
|
+
raise AccessUnavailable
|
435
|
+
end
|
436
|
+
nil
|
437
|
+
end
|
438
|
+
|
439
|
+
#---------------------------------
|
440
|
+
private
|
441
|
+
#---------------------------------
|
442
|
+
def _require_connection!
|
443
|
+
raise NotConnected unless is_connected?
|
444
|
+
end
|
445
|
+
|
446
|
+
def _find_characteristic(service_id, char_id)
|
447
|
+
uuid= _uuid_characteristic(char_id)
|
448
|
+
chars= _characteristics(service_id)
|
449
|
+
raise Service::NotFound, service_id if chars.nil?
|
450
|
+
char= chars[uuid]
|
451
|
+
raise Characteristic::NotFound, char_id if char.nil?
|
452
|
+
char
|
453
|
+
end
|
454
|
+
|
455
|
+
# @param service [String, Symbol] The id of the service.
|
456
|
+
# @return [Hash] The descriptions of the characteristics for the given service.
|
457
|
+
def _characteristics(service)
|
458
|
+
if srv = @services[_uuid_service(service)]
|
459
|
+
srv[:characteristics]
|
460
|
+
end
|
461
|
+
end
|
462
|
+
def _uuid_service(service)
|
463
|
+
uuid = case service
|
464
|
+
when UUID::REGEX
|
465
|
+
service.downcase
|
466
|
+
else
|
467
|
+
if i = Service[service]
|
468
|
+
i[:uuid]
|
469
|
+
end
|
470
|
+
end
|
471
|
+
if uuid.nil?
|
472
|
+
raise ArgumentError, "unable to get UUID for service"
|
473
|
+
end
|
474
|
+
|
475
|
+
uuid
|
476
|
+
end
|
477
|
+
def _uuid_characteristic(characteristic)
|
478
|
+
uuid = case characteristic
|
479
|
+
when UUID::REGEX
|
480
|
+
characteristic.downcase
|
481
|
+
else
|
482
|
+
if char = Characteristic[characteristic]
|
483
|
+
char.uuid
|
484
|
+
end
|
485
|
+
end
|
486
|
+
if uuid.nil?
|
487
|
+
raise ArgumentError, "unable to get UUID for characteristic"
|
488
|
+
end
|
489
|
+
|
490
|
+
uuid
|
491
|
+
end
|
492
|
+
|
493
|
+
end
|
494
|
+
end
|