ble 0.0.3 → 0.1.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 +4 -4
- data/lib/ble.rb +3 -0
- data/lib/ble/char_desc.rb +63 -0
- data/lib/ble/char_registry.rb +142 -0
- data/lib/ble/characteristic.rb +67 -136
- data/lib/ble/db_eddystone.rb +2 -2
- data/lib/ble/db_nordic.rb +2 -2
- data/lib/ble/db_sig_characteristic.rb +5 -5
- data/lib/ble/device.rb +259 -257
- data/lib/ble/notifications.rb +44 -0
- data/lib/ble/version.rb +9 -3
- metadata +5 -2
data/lib/ble/db_eddystone.rb
CHANGED
data/lib/ble/db_nordic.rb
CHANGED
@@ -10,8 +10,8 @@ end
|
|
10
10
|
|
11
11
|
|
12
12
|
module BLE
|
13
|
-
|
14
|
-
|
13
|
+
class Characteristic
|
14
|
+
add '00001531-1212-efde-1523-785feabcd123', # WriteWithoutResponse
|
15
15
|
name: 'DFU Packet'
|
16
16
|
|
17
17
|
add '00001532-1212-efde-1523-785feabcd123', # Write,Notify
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module BLE
|
2
|
-
|
2
|
+
class Characteristic
|
3
3
|
# C | Integer | 8-bit unsigned (unsigned char)
|
4
4
|
# S | Integer | 16-bit unsigned, native endian (uint16_t)
|
5
5
|
# L | Integer | 32-bit unsigned, native endian (uint32_t)
|
@@ -63,13 +63,13 @@ module Characteristic
|
|
63
63
|
type: 'org.bluetooth.characteristic.manufacturer_name_string',
|
64
64
|
in: ->(s) { s.force_encoding('UTF-8') },
|
65
65
|
out: ->(v) { v.encode('UTF-8') }
|
66
|
-
|
66
|
+
|
67
67
|
add 0x2A2A,
|
68
68
|
name: 'IEEE 11073-20601 Regulatory Certification Data List',
|
69
69
|
type: 'org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list',
|
70
70
|
in: ->(s) { raise NotYetImplemented },
|
71
71
|
out: ->(v) { raise NotYetImplemented }
|
72
|
-
|
72
|
+
|
73
73
|
add 0x2A50,
|
74
74
|
name: 'PnP ID',
|
75
75
|
type: 'org.bluetooth.characteristic.pnp_id',
|
@@ -83,8 +83,8 @@ module Characteristic
|
|
83
83
|
[ vendor_src, vendor_id,
|
84
84
|
product_id, product_version ] },
|
85
85
|
out: ->(v) { raise NotYetImplemented }
|
86
|
-
|
87
|
-
|
86
|
+
|
87
|
+
|
88
88
|
add 0x2A6E,
|
89
89
|
name: 'Temperature',
|
90
90
|
type: 'org.bluetooth.characteristic.temperature',
|
data/lib/ble/device.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
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
|
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
|
11
12
|
# Notify that you need to have the device in a connected state
|
12
13
|
class NotConnected < Error ; end
|
13
14
|
|
@@ -16,47 +17,47 @@ class Device
|
|
16
17
|
# @param auto_refresh [Boolean] gather information about device
|
17
18
|
# on connection
|
18
19
|
def initialize(adapter, dev, auto_refresh: true)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
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
44
|
end
|
45
|
+
end
|
45
46
|
end
|
46
47
|
|
47
48
|
# This removes the remote device object.
|
48
49
|
# It will remove also the pairing information.
|
49
50
|
# @return [Boolean]
|
50
51
|
def remove
|
51
|
-
|
52
|
-
|
52
|
+
@o_adapter[I_ADAPTER].RemoveDevice(@p_dev)
|
53
|
+
true
|
53
54
|
rescue DBus::Error => e
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
60
61
|
end
|
61
62
|
|
62
63
|
|
@@ -74,34 +75,34 @@ class Device
|
|
74
75
|
#
|
75
76
|
# @return [Boolean]
|
76
77
|
def pair
|
77
|
-
|
78
|
-
|
78
|
+
@o_dev[I_DEVICE].Pair
|
79
|
+
true
|
79
80
|
rescue DBus::Error => e
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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 NotAutorized
|
86
|
+
when E_AUTH_FAILED then raise NotAutorized
|
87
|
+
when E_AUTH_REJECTED then raise NotAutorized
|
88
|
+
when E_AUTH_TIMEOUT then raise NotAutorized
|
89
|
+
when E_AUTH_ATTEMPT_FAILED then raise NotAutorized
|
90
|
+
else raise ScriptError
|
91
|
+
end
|
91
92
|
end
|
92
93
|
|
93
94
|
# This method can be used to cancel a pairing
|
94
95
|
# operation initiated by the Pair method.
|
95
96
|
# @return [Boolean]
|
96
97
|
def cancel_pairing
|
97
|
-
|
98
|
-
|
98
|
+
@o_dev[I_DEVICE].CancelPairing
|
99
|
+
true
|
99
100
|
rescue DBus::Error => e
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
101
|
+
case e.name
|
102
|
+
when E_DOES_NOT_EXIST then true
|
103
|
+
when E_FAILED then false
|
104
|
+
else raise ScriptError
|
105
|
+
end
|
105
106
|
end
|
106
107
|
|
107
108
|
# This connect to the specified profile UUID or to any (:all)
|
@@ -113,26 +114,26 @@ class Device
|
|
113
114
|
# success.
|
114
115
|
# @return [Boolean]
|
115
116
|
def connect(profile=:all)
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
124
125
|
rescue DBus::Error => e
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
136
137
|
end
|
137
138
|
|
138
139
|
# This method gracefully disconnects :all connected profiles
|
@@ -146,51 +147,51 @@ class Device
|
|
146
147
|
# as long as the profile is registered this will always succeed
|
147
148
|
# @return [Boolean]
|
148
149
|
def disconnect(profile=:all)
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
157
158
|
rescue DBus::Error => e
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
172
173
|
end
|
173
174
|
|
174
175
|
# Indicates if the remote device is paired
|
175
176
|
def is_paired?
|
176
|
-
|
177
|
+
@o_dev[I_DEVICE]['Paired']
|
177
178
|
rescue DBus::Error => e
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
179
|
+
case e.name
|
180
|
+
when E_UNKNOWN_OBJECT
|
181
|
+
raise StalledObject
|
182
|
+
else raise ScriptError
|
183
|
+
end
|
183
184
|
end
|
184
185
|
|
185
186
|
# Indicates if the remote device is currently connected.
|
186
187
|
def is_connected?
|
187
|
-
|
188
|
+
@o_dev[I_DEVICE]['Connected']
|
188
189
|
rescue DBus::Error => e
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
190
|
+
case e.name
|
191
|
+
when E_UNKNOWN_OBJECT
|
192
|
+
raise StalledObject
|
193
|
+
else raise ScriptError
|
194
|
+
end
|
194
195
|
end
|
195
196
|
|
196
197
|
# List of available services as UUID.
|
@@ -211,14 +212,14 @@ class Device
|
|
211
212
|
#
|
212
213
|
# @return [Array<String>] List of service UUID
|
213
214
|
def services
|
214
|
-
|
215
|
-
|
215
|
+
_require_connection!
|
216
|
+
@services.keys
|
216
217
|
end
|
217
218
|
|
218
219
|
# Check if service is available on the device
|
219
220
|
# @return [Boolean]
|
220
221
|
def has_service?(service)
|
221
|
-
|
222
|
+
@service.key?(_uuid_service(service))
|
222
223
|
end
|
223
224
|
|
224
225
|
# List of available characteristics UUID for a service.
|
@@ -232,16 +233,16 @@ class Device
|
|
232
233
|
# connected if auto_refresh is enable, otherwise
|
233
234
|
# you need to call {#refresh}.
|
234
235
|
def characteristics(service)
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
236
|
+
_require_connection!
|
237
|
+
if chars = _characteristics(service)
|
238
|
+
chars.keys
|
239
|
+
end
|
239
240
|
end
|
240
241
|
|
241
242
|
# The Bluetooth device address of the remote device.
|
242
243
|
# @return [String] MAC address
|
243
244
|
def address
|
244
|
-
|
245
|
+
@o_dev[I_DEVICE]['Address']
|
245
246
|
end
|
246
247
|
|
247
248
|
# The Bluetooth remote name.
|
@@ -249,7 +250,7 @@ class Device
|
|
249
250
|
# devices name.
|
250
251
|
# @return [String] name
|
251
252
|
def name # optional
|
252
|
-
|
253
|
+
@o_dev[I_DEVICE]['Name']
|
253
254
|
end
|
254
255
|
|
255
256
|
# The name alias for the remote device.
|
@@ -258,20 +259,20 @@ class Device
|
|
258
259
|
# In case no alias is set, it will return the remote device name.
|
259
260
|
# @return [String]
|
260
261
|
def alias
|
261
|
-
|
262
|
+
@o_dev[I_DEVICE]['Alias']
|
262
263
|
end
|
263
264
|
# Setting an empty string or nil as alias will convert it
|
264
265
|
# back to the remote device name.
|
265
266
|
# @param val [String, nil]
|
266
267
|
# @return [void]
|
267
268
|
def alias=(val)
|
268
|
-
|
269
|
+
@o_dev[I_DEVICE]['Alias'] = val.nil? ? "" : val.to_str
|
269
270
|
end
|
270
271
|
|
271
272
|
# Is the device trusted?
|
272
273
|
# @return [Boolean]
|
273
274
|
def is_trusted?
|
274
|
-
|
275
|
+
@o_dev[I_DEVICE]['Trusted']
|
275
276
|
end
|
276
277
|
|
277
278
|
# Indicates if the remote is seen as trusted. This
|
@@ -279,16 +280,16 @@ class Device
|
|
279
280
|
# @param val [Boolean]
|
280
281
|
# @return [void]
|
281
282
|
def trusted=(val)
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
283
|
+
if ! [ true, false ].include?(val)
|
284
|
+
raise ArgumentError, "value must be a boolean"
|
285
|
+
end
|
286
|
+
@o_dev[I_DEVICE]['Trusted'] = val
|
286
287
|
end
|
287
288
|
|
288
289
|
# Is the device blocked?
|
289
290
|
# @return [Boolean]
|
290
291
|
def is_blocked?
|
291
|
-
|
292
|
+
@o_dev[I_DEVICE]['Blocked']
|
292
293
|
end
|
293
294
|
|
294
295
|
# If set to true any incoming connections from the
|
@@ -298,116 +299,110 @@ class Device
|
|
298
299
|
# @param val [Boolean]
|
299
300
|
# @return [void]
|
300
301
|
def blocked=(val)
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
302
|
+
if ! [ true, false ].include?(val)
|
303
|
+
raise ArgumentError, "value must be a boolean"
|
304
|
+
end
|
305
|
+
@o_dev[I_DEVICE]['Blocked'] = val
|
305
306
|
end
|
306
307
|
|
307
308
|
# Received Signal Strength Indicator of the remote
|
308
309
|
# device (inquiry or advertising).
|
309
310
|
# @return [Integer]
|
310
311
|
def rssi # optional
|
311
|
-
|
312
|
+
@o_dev[I_DEVICE]['RSSI']
|
312
313
|
rescue DBus::Error => e
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
314
|
+
case e.name
|
315
|
+
when E_INVALID_ARGS then raise NotSupported
|
316
|
+
else raise ScriptError
|
317
|
+
end
|
317
318
|
end
|
318
319
|
|
319
320
|
# Advertised transmitted power level (inquiry or advertising).
|
320
321
|
# @return [Integer]
|
321
322
|
def tx_power # optional
|
322
|
-
|
323
|
+
@o_dev[I_DEVICE]['TxPower']
|
323
324
|
rescue DBus::Error => e
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
325
|
+
case e.name
|
326
|
+
when E_INVALID_ARGS then raise NotSupported
|
327
|
+
else raise ScriptError
|
328
|
+
end
|
328
329
|
end
|
329
330
|
|
330
331
|
|
331
332
|
# Refresh list of services and characteristics
|
332
333
|
# @return [Boolean]
|
333
334
|
def refresh
|
334
|
-
|
335
|
-
|
335
|
+
refresh!
|
336
|
+
true
|
336
337
|
rescue NotConnected, StalledObject
|
337
|
-
|
338
|
+
false
|
338
339
|
end
|
339
340
|
|
340
341
|
# Refresh list of services and characteristics
|
341
342
|
# @raise [NotConnected] if device is not in a connected state
|
342
343
|
# @return [self]
|
343
344
|
def refresh!
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
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 }) ]
|
356
357
|
}]
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
358
|
+
uuid = srv['UUID'].downcase
|
359
|
+
[ uuid, { :uuid => uuid,
|
360
|
+
:primary => srv['Primary'],
|
361
|
+
:characteristics => char } ]
|
362
|
+
}]
|
363
|
+
self
|
363
364
|
rescue DBus::Error => e
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
end
|
373
|
-
raise NotReady
|
374
|
-
|
375
|
-
else raise ScriptError
|
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
|
376
373
|
end
|
374
|
+
raise NotReady
|
375
|
+
|
376
|
+
else raise ScriptError
|
377
|
+
end
|
377
378
|
end
|
378
379
|
|
379
380
|
# Get value for a service/characteristic.
|
380
381
|
#
|
381
382
|
# @param service [String, Symbol]
|
382
383
|
# @param characteristic [String, Symbol]
|
383
|
-
# @param raw [Boolean]
|
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, ...)
|
384
385
|
# @raise [NotConnected] if device is not in a connected state
|
385
386
|
# @raise [NotYetImplemented] encryption is not implemented yet
|
386
387
|
# @raise [Service::NotFound, Characteristic::NotFound] if service/characteristic doesn't exist on this device
|
387
388
|
# @raise [AccessUnavailable] if not available for reading
|
388
389
|
# @return [Object]
|
389
390
|
def [](service, characteristic, raw: false)
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
elsif flags.include?('encrypt-read') ||
|
406
|
-
flags.include?('encrypt-authenticated-read')
|
407
|
-
raise NotYetImplemented
|
408
|
-
else
|
409
|
-
raise AccessUnavailable
|
410
|
-
end
|
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
|
411
406
|
end
|
412
407
|
|
413
408
|
# Set value for a service/characteristic
|
@@ -415,78 +410,85 @@ class Device
|
|
415
410
|
# @param service [String, Symbol]
|
416
411
|
# @param characteristic [String, Symbol]
|
417
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, ...).
|
418
414
|
# @raise [NotConnected] if device is not in a connected state
|
419
415
|
# @raise [NotYetImplemented] encryption is not implemented yet
|
420
416
|
# @raise [Service::NotFound, Characteristic::NotFound] if service/characteristic doesn't exist on this device
|
421
417
|
# @raise [AccessUnavailable] if not available for writing
|
422
418
|
# @return [void]
|
423
419
|
def []=(service, characteristic, val, raw: false)
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
val = info[:out].call(val) if info[:out]
|
442
|
-
end
|
443
|
-
val = val.unpack('C*')
|
444
|
-
obj[I_GATT_CHARACTERISTIC].WriteValue(val)
|
445
|
-
elsif flags.include?('encrypt-write') ||
|
446
|
-
flags.include?('encrypt-authenticated-write')
|
447
|
-
raise NotYetImplemented
|
448
|
-
else
|
449
|
-
raise AccessUnavailable
|
450
|
-
end
|
451
|
-
nil
|
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
|
452
437
|
end
|
453
438
|
|
439
|
+
#---------------------------------
|
454
440
|
private
|
441
|
+
#---------------------------------
|
442
|
+
def _require_connection!
|
443
|
+
raise NotConnected unless is_connected?
|
444
|
+
end
|
455
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.
|
456
457
|
def _characteristics(service)
|
457
|
-
|
458
|
-
|
459
|
-
|
458
|
+
if srv = @services[_uuid_service(service)]
|
459
|
+
srv[:characteristics]
|
460
|
+
end
|
460
461
|
end
|
461
462
|
def _uuid_service(service)
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
end
|
469
|
-
end
|
470
|
-
if uuid.nil?
|
471
|
-
raise ArgumentError, "unable to get UUID for service"
|
463
|
+
uuid = case service
|
464
|
+
when UUID::REGEX
|
465
|
+
service.downcase
|
466
|
+
else
|
467
|
+
if i = Service[service]
|
468
|
+
i[:uuid]
|
472
469
|
end
|
470
|
+
end
|
471
|
+
if uuid.nil?
|
472
|
+
raise ArgumentError, "unable to get UUID for service"
|
473
|
+
end
|
473
474
|
|
474
|
-
|
475
|
+
uuid
|
475
476
|
end
|
476
477
|
def _uuid_characteristic(characteristic)
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
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
|
490
491
|
end
|
491
|
-
|
492
|
+
|
493
|
+
end
|
492
494
|
end
|