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 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



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
44
45
46
# File 'lib/ble/device.rb', line 19

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 Method Details

#[](service, characteristic, raw: false) ⇒ Object

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:



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/ble/device.rb', line 390

def [](service, characteristic, raw: 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')
    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) ⇒ void

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:



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/ble/device.rb', line 419

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

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

#addressString

The Bluetooth device address of the remote device.

Returns:

  • (String)

    MAC address



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

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)


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

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)


268
269
270
# File 'lib/ble/device.rb', line 268

def alias=(val)
  @o_dev[I_DEVICE]['Alias'] = val.nil? ? "" : val.to_str
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)


301
302
303
304
305
306
# File 'lib/ble/device.rb', line 301

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)


97
98
99
100
101
102
103
104
105
106
# File 'lib/ble/device.rb', line 97

def cancel_pairing
  @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:



235
236
237
238
239
240
# File 'lib/ble/device.rb', line 235

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)


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/ble/device.rb', line 116

def connect(profile=:all)
  case profile
  when UUID::REGEX
    @o_dev[I_DEVICE].ConnectProfile(profile)
  when :all
    @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)


149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/ble/device.rb', line 149

def disconnect(profile=:all)
  case profile
  when UUID::REGEX
    @o_dev[I_DEVICE].DisconnectProfile(profile)
  when :all
    @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)


221
222
223
# File 'lib/ble/device.rb', line 221

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

#is_blocked?Boolean

Is the device blocked?

Returns:

  • (Boolean)


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

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

#is_connected?Boolean

Indicates if the remote device is currently connected.

Returns:

  • (Boolean)


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

def is_connected?
  @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)


176
177
178
179
180
181
182
183
184
# File 'lib/ble/device.rb', line 176

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)


274
275
276
# File 'lib/ble/device.rb', line 274

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



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

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)


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/ble/device.rb', line 77

def pair
  @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 NotAutorized
  when E_AUTH_FAILED            then raise NotAutorized
  when E_AUTH_REJECTED          then raise NotAutorized
  when E_AUTH_TIMEOUT           then raise NotAutorized
  when E_AUTH_ATTEMPT_FAILED    then raise NotAutorized
  else raise ScriptError
  end
end

#refreshBoolean

Refresh list of services and characteristics

Returns:

  • (Boolean)


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

def refresh
  refresh!
  true
rescue NotConnected, StalledObject
  false
end

#refresh!self

Refresh list of services and characteristics

Returns:

  • (self)

Raises:



344
345
346
347
348
349
350
351
352
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
# File 'lib/ble/device.rb', line 344

def refresh!
  _require_connection!
  max_wait ||= 1.5  # Use ||= due to the retry
  @services = Hash[@o_dev[I_DEVICE]['GattServices'].map {|p_srv|
      o_srv = BLUEZ.object(p_srv)
      o_srv.introspect
      srv   = o_srv[I_PROPERTIES].GetAll(I_GATT_SERVICE).first
      char  = Hash[srv['Characteristics'].map {|p_char|
          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)


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

def remove
  @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)


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

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:



214
215
216
217
# File 'lib/ble/device.rb', line 214

def services
  _require_connection!
  @services.keys
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)


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

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)


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

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