Class: BLE::Device

Inherits:
Object
  • Object
show all
Includes:
Notifications
Defined in:
lib/ble/device.rb

Overview

Create de Device object

d = Device::new('hci0', 'aa:bb:dd:dd:ee:ff')
d = Adapter.new('hci0')['aa:bb:dd:dd:ee:ff']

d.services
d.characteristics(:environmental_sensing)
d[:environmental_sensing, :temperature]

Defined Under Namespace

Classes: NotConnected

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Notifications

#on_notification, #start_notify!

Constructor Details

#initialize(adapter, dev, auto_refresh: true) ⇒ Device

Returns a new instance of Device.

Parameters:

  • adapter (String)

    adapter unix device name

  • dev (String)

    device MAC address

  • auto_refresh (Boolean) (defaults to: true)

    gather information about device on connection


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ble/device.rb', line 21

def initialize(adapter, dev, auto_refresh: true)
  @adapter, @dev = adapter, dev
  @auto_refresh  = auto_refresh
  @services      = {}

  @n_adapter = adapter
  @p_adapter = "/org/bluez/#{@n_adapter}"
  @o_adapter = BLUEZ.object(@p_adapter)
  @o_adapter.introspect

  @n_dev     = 'dev_' + dev.tr(':', '_')
  @p_dev     = "/org/bluez/#{@n_adapter}/#{@n_dev}"
  @o_dev     = BLUEZ.object(@p_dev)
  @o_dev.introspect

  self.refresh if @auto_refresh

  @o_dev[I_PROPERTIES]
  .on_signal('PropertiesChanged') do |intf, props|
    case intf
    when I_DEVICE
      case props['Connected']
      when true
        self.refresh if @auto_refresh
      end
    end
  end
end

Instance Attribute Details

#requires_connectionObject

Returns the value of attribute requires_connection


15
16
17
# File 'lib/ble/device.rb', line 15

def requires_connection
  @requires_connection
end

Instance Method Details

#[](service, characteristic, raw: false, async: false) ⇒ Object Also known as: read

Get value for a service/characteristic.

Parameters:

  • service (String, Symbol)
  • characteristic (String, Symbol)
  • raw (Boolean) (defaults to: false)

    When raw is true the value get is a binary string, instead of an object corresponding to the decoded characteristic (float, integer, array, …)

Returns:

  • (Object)

Raises:


401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/ble/device.rb', line 401

def [](service, characteristic, raw: false, async: false)
  _require_connection!
  uuid  = _uuid_characteristic(characteristic)
  chars = _characteristics(service)
  raise Service::NotFound,        service        if chars.nil?
  char  = chars[uuid]
  raise Characteristic::NotFound, characteristic if char.nil?

  if char.flag?('read')
     async ? char.async_read(raw: raw) : char.read(raw: raw)
  elsif char.flag?('encrypt-read') ||
      char.flag?('encrypt-authenticated-read')
    raise NotYetImplemented
  else
    raise AccessUnavailable
  end
end

#[]=(service, characteristic, val, raw: false, async: false) ⇒ void Also known as: write

This method returns an undefined value.

Set value for a service/characteristic

Parameters:

  • service (String, Symbol)
  • characteristic (String, Symbol)
  • val (Boolean)
  • raw (Boolean) (defaults to: false)

    When raw is true the value set is a binary string, instead of an object corresponding to the decoded characteristic (float, integer, array, …).

Raises:


434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/ble/device.rb', line 434

def []=(service, characteristic, val, raw: false, async: false)
  _require_connection!
  uuid  = _uuid_characteristic(characteristic)
  chars = _characteristics(service)
  raise Service::NotFound,        service        if chars.nil?
  char  = chars[uuid]
  raise Characteristic::NotFound, characteristic if char.nil?

  if char.flag?('write') ||
    char.flag?('write-without-response')
    async ? char.async_write(val, raw: raw) : char.write(val, raw: raw)
  elsif char.flag?('encrypt-write') ||
      char.flag?('encrypt-authenticated-write')
    raise NotYetImplemented
  else
    raise AccessUnavailable
  end
end

#addressString

The Bluetooth device address of the remote device.

Returns:

  • (String)

    MAC address


253
254
255
# File 'lib/ble/device.rb', line 253

def address
  @o_dev[I_DEVICE]['Address']
end

#aliasString

The name alias for the remote device. The alias can be used to have a different friendly name for the remote device. In case no alias is set, it will return the remote device name.

Returns:

  • (String)

270
271
272
# File 'lib/ble/device.rb', line 270

def alias
  @o_dev[I_DEVICE]['Alias']
end

#alias=(val) ⇒ void

This method returns an undefined value.

Setting an empty string or nil as alias will convert it back to the remote device name.

Parameters:

  • val (String, nil)

277
278
279
# File 'lib/ble/device.rb', line 277

def alias=(val)
  @o_dev[I_DEVICE]['Alias'] = val.nil? ? "" : val.to_str
end

#async_read(service, characteristic, raw: false) ⇒ Object


419
420
421
# File 'lib/ble/device.rb', line 419

def async_read service, characteristic, raw: false
  read service, characteristic, raw: raw, async: true
end

#async_write(service, characteristic, val, raw: false) ⇒ Object


453
454
455
# File 'lib/ble/device.rb', line 453

def async_write(service, characteristic, val,  raw: false)
  write service, characteristic, val, raw: raw, async: true
end

#blocked=(val) ⇒ void

This method returns an undefined value.

If set to true any incoming connections from the device will be immediately rejected. Any device drivers will also be removed and no new ones will be probed as long as the device is blocked

Parameters:

  • val (Boolean)

310
311
312
313
314
315
# File 'lib/ble/device.rb', line 310

def blocked=(val)
  if ! [ true, false ].include?(val)
    raise ArgumentError, "value must be a boolean"
  end
  @o_dev[I_DEVICE]['Blocked'] = val
end

#cancel_pairingBoolean

This method can be used to cancel a pairing operation initiated by the Pair method.

Returns:

  • (Boolean)

101
102
103
104
105
106
107
108
109
110
111
# File 'lib/ble/device.rb', line 101

def cancel_pairing
  block_given? ? @o_dev[I_DEVICE].CancelPairing(&Proc.new) :
                 @o_dev[I_DEVICE].CancelPairing()
  true
rescue DBus::Error => e
  case e.name
  when E_DOES_NOT_EXIST then true
  when E_FAILED         then false
  else raise ScriptError
  end
end

#characteristics(service) ⇒ Array<String>?

Note:

The list is retrieve once when object is connected if auto_refresh is enable, otherwise you need to call #refresh.

List of available characteristics UUID for a service.

Parameters:

  • service

    service can be a UUID, a service type or a service nickname

Returns:

  • (Array<String>, nil)

    list of characteristics or nil if the service doesn't exist

Raises:


244
245
246
247
248
249
# File 'lib/ble/device.rb', line 244

def characteristics(service)
  _require_connection!
  if chars = _characteristics(service)
    chars.keys
  end
end

#connect(profile = :all) ⇒ Boolean

This connect to the specified profile UUID or to any (:all) profiles the remote device supports that can be connected to and have been flagged as auto-connectable on our side. If only subset of profiles is already connected it will try to connect currently disconnected ones. If at least one profile was connected successfully this method will indicate success.

Returns:

  • (Boolean)

121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/ble/device.rb', line 121

def connect(profile=:all)
  case profile
  when UUID::REGEX
    block_given? ? @o_dev[I_DEVICE].ConnectProfile(profile, &Proc.new) :
                   @o_dev[I_DEVICE].ConnectProfile(profile)
  when :all
    block_given? ? @o_dev[I_DEVICE].Connect(&Proc.new) :
                   @o_dev[I_DEVICE].Connect()
  else raise ArgumentError, "profile uuid or :all expected"
  end
  true
rescue DBus::Error => e
  case e.name
  when E_NOT_READY
  when E_FAILED
  when E_IN_PROGRESS
    false
  when E_ALREADY_CONNECTED
    true
  when E_UNKNOWN_OBJECT
    raise StalledObject
  else raise ScriptError
  end
end

#disconnect(profile = :all) ⇒ Boolean

This method gracefully disconnects :all connected profiles and then terminates low-level ACL connection. ACL connection will be terminated even if some profiles were not disconnected properly e.g. due to misbehaving device. This method can be also used to cancel a preceding #connect call before a reply to it has been received. If a profile UUID is specified, only this profile is disconnected, and as their is no connection tracking for a profile, so as long as the profile is registered this will always succeed

Returns:

  • (Boolean)

156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ble/device.rb', line 156

def disconnect(profile=:all)
  case profile
  when UUID::REGEX
    block_given? ? @o_dev[I_DEVICE].DisconnectProfile(profile, &Proc.new) :
                   @o_dev[I_DEVICE].DisconnectProfile(profile)
  when :all
    block_given? ? @o_dev[I_DEVICE].Disconnect(&Proc.new) :
                   @o_dev[I_DEVICE].Disconnect()
  else raise ArgumentError, "profile uuid or :all expected"
  end
  true
rescue DBus::Error => e
  case e.name
  when E_FAILED
  when E_IN_PROGRESS
    false
  when E_INVALID_ARGUMENTS
    raise ArgumentError, "unsupported profile (#{profile})"
  when E_NOT_SUPPORTED
    raise NotSupported
  when E_NOT_CONNECTED
    true
  when E_UNKNOWN_OBJECT
    raise StalledObject
  else raise ScriptError
  end
end

#has_service?(service) ⇒ Boolean

Check if service is available on the device

Returns:

  • (Boolean)

230
231
232
# File 'lib/ble/device.rb', line 230

def has_service?(service)
  @service.key?(_uuid_service(service))
end

#is_blocked?Boolean

Is the device blocked?

Returns:

  • (Boolean)

300
301
302
# File 'lib/ble/device.rb', line 300

def is_blocked?
  @o_dev[I_DEVICE]['Blocked']
end

#is_connected?Boolean

Indicates if the remote device is currently connected.

Returns:

  • (Boolean)

196
197
198
199
200
201
202
203
204
# File 'lib/ble/device.rb', line 196

def is_connected?
  !@requires_connection || @o_dev[I_DEVICE]['Connected']
rescue DBus::Error => e
  case e.name
  when E_UNKNOWN_OBJECT
    raise StalledObject
  else raise ScriptError
  end
end

#is_paired?Boolean

Indicates if the remote device is paired

Returns:

  • (Boolean)

185
186
187
188
189
190
191
192
193
# File 'lib/ble/device.rb', line 185

def is_paired?
  @o_dev[I_DEVICE]['Paired']
rescue DBus::Error => e
  case e.name
  when E_UNKNOWN_OBJECT
    raise StalledObject
  else raise ScriptError
  end
end

#is_trusted?Boolean

Is the device trusted?

Returns:

  • (Boolean)

283
284
285
# File 'lib/ble/device.rb', line 283

def is_trusted?
  @o_dev[I_DEVICE]['Trusted']
end

#nameString

The Bluetooth remote name. It is better to always use the #alias when displaying the devices name.

Returns:

  • (String)

    name


261
262
263
# File 'lib/ble/device.rb', line 261

def name # optional
  @o_dev[I_DEVICE]['Name']
end

#pairBoolean

This method will connect to the remote device, initiate pairing and then retrieve all SDP records (or GATT primary services). If the application has registered its own agent, then that specific agent will be used. Otherwise it will use the default agent. Only for applications like a pairing wizard it would make sense to have its own agent. In almost all other cases the default agent will handle this just fine. In case there is no application agent and also no default agent present, this method will fail.

Returns:

  • (Boolean)

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ble/device.rb', line 80

def pair
  block_given? ? @o_dev[I_DEVICE].Pair(&Proc.new) :
                 @o_dev[I_DEVICE].Pair()
  true
rescue DBus::Error => e
  case e.name
  when E_INVALID_ARGUMENTS      then false
  when E_FAILED                 then false
  when E_ALREADY_EXISTS         then true
  when E_AUTH_CANCELED          then raise NotAuthorized
  when E_AUTH_FAILED            then raise NotAuthorized
  when E_AUTH_REJECTED          then raise NotAuthorized
  when E_AUTH_TIMEOUT           then raise NotAuthorized
  when E_AUTH_ATTEMPT_FAILED    then raise NotAuthorized
  else raise ScriptError
  end
end

#refreshBoolean

Refresh list of services and characteristics

Returns:

  • (Boolean)

343
344
345
346
347
348
# File 'lib/ble/device.rb', line 343

def refresh
  refresh!
  true
rescue NotConnected, StalledObject
  false
end

#refresh!self

Refresh list of services and characteristics

Returns:

  • (self)

Raises:


353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/ble/device.rb', line 353

def refresh!
  _require_connection!
  max_wait ||= 1.5 # Use ||= due to the retry
  @services = Hash[@o_dev.subnodes.map {|p_srv|
      p_srv = [@o_dev.path, p_srv].join '/'
      o_srv = BLUEZ.object(p_srv)
      o_srv.introspect
      srv   = o_srv.GetAll(I_GATT_SERVICE).first
      char  = Hash[o_srv.subnodes.map {|char|
          p_char = [o_srv.path, char].join '/'
          o_char = BLUEZ.object(p_char)
          o_char.introspect
          uuid  = o_char[I_GATT_CHARACTERISTIC]['UUID' ].downcase
          flags = o_char[I_GATT_CHARACTERISTIC]['Flags']
          [ uuid, Characteristic.new({ :uuid => uuid, :flags => flags, :obj => o_char }) ]
        }]
      uuid = srv['UUID'].downcase
      [ uuid, { :uuid            => uuid,
          :primary         => srv['Primary'],
          :characteristics => char } ]
    }]
  self
rescue DBus::Error => e
  case e.name
  when E_UNKNOWN_OBJECT
    raise StalledObject
  when E_INVALID_ARGS
    # That's probably because all the bluez information
    # haven't been collected yet on dbus for GattServices
    if max_wait > 0
      sleep(0.25) ; max_wait -= 0.25 ; retry
    end
    raise NotReady

  else raise ScriptError
  end
end

#removeBoolean

This removes the remote device object. It will remove also the pairing information.

Returns:

  • (Boolean)

53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ble/device.rb', line 53

def remove
  block_given? ? @o_adapter[I_ADAPTER].RemoveDevice(@p_dev, &Proc.new) :
                 @o_adapter[I_ADAPTER].RemoveDevice(@p_dev)
  true
rescue DBus::Error => e
  case e.name
  when E_FAILED         then false
  when E_DOES_NOT_EXIST then raise StalledObject
  when E_UNKNOWN_OBJECT then raise StalledObject
  else raise ScriptError
  end
end

#rssiInteger

Received Signal Strength Indicator of the remote device (inquiry or advertising).

Returns:

  • (Integer)

320
321
322
323
324
325
326
327
# File 'lib/ble/device.rb', line 320

def rssi # optional
  @o_dev[I_DEVICE]['RSSI']
rescue DBus::Error => e
  case e.name
  when E_INVALID_ARGS then raise NotSupported
  else raise ScriptError
  end
end

#servicesArray<String>

Note:

The list is retrieve once when object is connected if auto_refresh is enable, otherwise you need to call #refresh.

Note:

This is the list of UUIDs for which we have an entry in the underlying api (bluez-dbus), which can be less that the list of advertised UUIDs.

List of available services as UUID.

Examples:

list available services

$d.services.each {|uuid|
  info = BLE::Service[uuid]
  name = info.nil? ? uuid : info[:name]
  puts name
}

Returns:

  • (Array<String>)

    List of service UUID

Raises:


223
224
225
226
# File 'lib/ble/device.rb', line 223

def services
  _require_connection!
  @services.keys
end

#subscribe(service, characteristic, raw: false, &block) ⇒ Object

Raises:


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/ble/device.rb', line 457

def subscribe(service, characteristic, raw: false, &block)
  _require_connection!
  uuid  = _uuid_characteristic(characteristic)
  chars = _characteristics(service)
  raise Service::NotFound,        service        if chars.nil?
  char  = chars[uuid]
  raise Characteristic::NotFound, characteristic if char.nil?

  if char.flag? 'notify'
    char.notify!
    char.on_change(raw: raw, &block)
  else
     raise OperationNotSupportedError.new("No notifications available for characteristic #{characteristic}")
  end
end

#trusted=(val) ⇒ void

This method returns an undefined value.

Indicates if the remote is seen as trusted. This setting can be changed by the application.

Parameters:

  • val (Boolean)

291
292
293
294
295
296
# File 'lib/ble/device.rb', line 291

def trusted=(val)
  if ! [ true, false ].include?(val)
    raise ArgumentError, "value must be a boolean"
  end
  @o_dev[I_DEVICE]['Trusted'] = val
end

#tx_powerInteger

Advertised transmitted power level (inquiry or advertising).

Returns:

  • (Integer)

331
332
333
334
335
336
337
338
# File 'lib/ble/device.rb', line 331

def tx_power # optional
  @o_dev[I_DEVICE]['TxPower']
rescue DBus::Error => e
  case e.name
  when E_INVALID_ARGS then raise NotSupported
  else raise ScriptError
  end
end