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.
@@ -0,0 +1,46 @@
1
+ module BLE
2
+ class Agent < DBus::Object
3
+ @log = Logger.new($stdout)
4
+ # https://kernel.googlesource.com/pub/scm/bluetooth/bluez/+/refs/heads/master/doc/agent-api.txt
5
+ dbus_interface I_AGENT do
6
+ dbus_method :Release do
7
+ @log.debug "Release()"
8
+ exit false
9
+ end
10
+
11
+ dbus_method :RequestPinCode, "in device:o, out ret:s" do |device|
12
+ @log.debug{ "RequestPinCode(#{device})" }
13
+ ["0000"]
14
+ end
15
+
16
+ dbus_method :RequestPasskey, "in device:o, out ret:u" do |device|
17
+ @log.debug{ "RequestPasskey(#{device})" }
18
+ raise DBus.error("org.bluez.Error.Rejected")
19
+ end
20
+
21
+ dbus_method :DisplayPasskey, "in device:o, in passkey:u, in entered:y" do |device, passkey, entered|
22
+ @log.debug{ "DisplayPasskey(#{device}, #{passkey}, #{entered})" }
23
+ raise DBus.error("org.bluez.Error.Rejected")
24
+ end
25
+
26
+ dbus_method :RequestConfirmation, "in device:o, in passkey:u" do |device, passkey|
27
+ @log.debug{ "RequestConfirmation(#{device}, #{passkey})" }
28
+ raise DBus.error("org.bluez.Error.Rejected")
29
+ end
30
+
31
+ dbus_method :Authorize, "in device:o, in uuid:s" do |device, uuid|
32
+ @log.debug{ "Authorize(#{device}, #{uuid})" }
33
+ end
34
+
35
+ dbus_method :ConfirmModeChange, "in mode:s" do |mode|
36
+ @log.debug{ "ConfirmModeChange(#{mode})" }
37
+ raise DBus.error("org.bluez.Error.Rejected")
38
+ end
39
+
40
+ dbus_method :Cancel do
41
+ @log.debug "Cancel()"
42
+ raise DBus.error("org.bluez.Error.Rejected")
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ # Encapsulates Characteristic descriptors and offers easy
2
+ # access to characteristic related information.
3
+ #
4
+ module BLE
5
+ class CharDesc
6
+ attr_reader :desc
7
+ # @param desc [Hash] Descriptors.
8
+ def initialize(desc)
9
+ @desc= desc
10
+ end
11
+ def flags
12
+ @flags||= @desc[:flags]
13
+ end
14
+ def config
15
+ @config||= Characteristic[@desc[:uuid]]
16
+ end
17
+
18
+ def uuid
19
+ @desc[:uuid]
20
+ end
21
+ def flag?(flag_name)
22
+ flags.include?(flag_name)
23
+ end
24
+
25
+ # Does outgoing values have processors configured?
26
+ # If yes, the value needs to be pre-processed before being send.
27
+ def write_processors?
28
+ verifier? or write_pre_processor?
29
+ end
30
+ # Does incoming values have processors configured?
31
+ # If yes, the value needs to be post-processed after being received.
32
+ def read_processors?
33
+ config && read_post_processor?
34
+ end
35
+ # It has been configured a verifier preprocessor to check
36
+ # outgoing data?
37
+ def verifier?
38
+ config[:vrfy]
39
+ end
40
+ def write_pre_processor?
41
+ config[:out]
42
+ end
43
+ def read_post_processor?
44
+ config[:in]
45
+ end
46
+ # Is the received value verified by the verifier?
47
+ def verifies?(val)
48
+ config[:vrfy].call(val)
49
+ end
50
+ def pre_process(val)
51
+ if verifier? && !verifies?(val)
52
+ raise ArgumentError,
53
+ "bad value for characteristic '#{uuid}'"
54
+ end
55
+ val = config[:out].call(val) if write_pre_processor?
56
+ end
57
+
58
+ def post_process(val)
59
+ val = config[:in].call(val) if read_post_processor?
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,142 @@
1
+ # To add a new characteristic description:
2
+ # BLE::Characteristic.add 'c4935338-4307-47cf-ae1f-feac9e2b3ae7',
3
+ # name: 'Controlled mind',
4
+ # type: 'net.cortex-minus.characteristic.controlled_mind'
5
+ # vrfy: ->(x) { x >= 0 },
6
+ # in: ->(s) { s.unpack('s<').first },
7
+ # out: ->(v) { [ v ].pack('s<') }
8
+ #
9
+ # Returned characteristic description will be a hash:
10
+ # {
11
+ # name: "Bluetooth characteristic name",
12
+ # type: "org.bluetooth.characteristic.name",
13
+ # uuid: "128bit-uuid-string",
14
+ # vrfy: ->(x) { verify_value(x) },
15
+ # in: ->(s) { s.unpack(....) ... },
16
+ # out: ->(v) { [ v ].pack(....) ... }
17
+ # }
18
+ #
19
+ module BLE
20
+ module CharRegistry
21
+
22
+ private
23
+
24
+ DB_UUID = {}
25
+ DB_TYPE = {}
26
+ DB_NICKNAME = {}
27
+
28
+ public
29
+ def self.included(klass)
30
+ class << klass
31
+ # Get characteristic description from nickname.
32
+ #
33
+ # @param id [Symbol] nickname
34
+ # @return [CharDesc] characteristic description
35
+ def by_nickname(id)
36
+ DB_NICKNAME[id]
37
+ end
38
+
39
+ # Get characteristic description from uuid.
40
+ #
41
+ # @param id [String] uuid
42
+ # @return [CharDesc] characteristic description
43
+ def by_uuid(id)
44
+ DB_UUID[id]
45
+ end
46
+
47
+ # Get characteristic description from type
48
+ #
49
+ # @param id [Strig] type
50
+ # @return [CharDesc] characteristic description
51
+ def by_type(id)
52
+ DB_TYPE[id]
53
+ end
54
+
55
+ # Get a characteristic description from it's id
56
+ # @param id [Symbol,String]
57
+ # @return [CharDesc]
58
+ def [](id)
59
+ case id
60
+ when Symbol then DB_NICKNAME[id]
61
+ when UUID::REGEX then DB_UUID[id]
62
+ when String then DB_TYPE[id]
63
+ when Integer then DB_UUID[BLE::UUID(id)]
64
+ else raise ArgumentError, "invalid type for characteristic id"
65
+ end
66
+ end
67
+
68
+
69
+ # Add a characteristic description.
70
+ # @example Add a characteristic description with a 16-bit uuid
71
+ # module Characteristic
72
+ # add 0x2A6E,
73
+ # name: 'Temperature',
74
+ # type: 'org.bluetooth.characteristic.temperature',
75
+ # vrfy: ->(x) { (0..100).include?(x) },
76
+ # in: ->(s) { s.unpack('s<').first.to_f / 100 },
77
+ # out: ->(v) { [ v*100 ].pack('s<') }
78
+ # end
79
+ #
80
+ # @example Add a characteristic description with a 128-bit uuid
81
+ # module Characteristic
82
+ # add 'c4935338-4307-47cf-ae1f-feac9e2b3ae7',
83
+ # name: 'Controlled mind',
84
+ # type: 'net.cortex-minus.characteristic.controlled_mind',
85
+ # vrfy: ->(x) { x >= 0 },
86
+ # in: ->(s) { s.unpack('s<').first },
87
+ # out: ->(v) { [ v ].pack('s<') }
88
+ # end
89
+ #
90
+ # @param uuid [Integer, String] 16-bit, 32-bit or 128-bit uuid
91
+ # @param name [String]
92
+ # @option opts :type [String] type
93
+ # @option opts :nick [Symbol] nickname
94
+ # @option opts :in [Proc] convert to ruby
95
+ # @option opts :out [Proc] convert to bluetooth data
96
+ # @option opts :vry [Proc] verify
97
+ # @return [CharDesc] characteristic description
98
+ def add(uuid, name:, **opts)
99
+ uuid = BLE::UUID(uuid)
100
+ type = opts.delete :type
101
+ nick = opts.delete :nick
102
+ _in = opts.delete :in
103
+ _out = opts.delete :out
104
+ vrfy = opts.delete :vrfy
105
+ if opts.first
106
+ raise ArgumentError, "unknown keyword: #{opts.first[0]}"
107
+ end
108
+
109
+ char_config= DB_UUID[uuid] = {
110
+ uuid: uuid,
111
+ name: name,
112
+ in: _in,
113
+ out: _out,
114
+ vrfy: vrfy
115
+ }
116
+
117
+ # Add type if specified
118
+ if type
119
+ type = type.downcase
120
+ char_config.merge!(type: type)
121
+ DB_TYPE[type] = char_config
122
+ end
123
+
124
+ # Add nickname if specified or can be derived from type
125
+ if nick.nil? && type && type =~ /\.(?<key>[^.]+)$/
126
+ nick = $1.to_sym if type.start_with? 'org.bluetooth.characteristic'
127
+ end
128
+ if nick
129
+ if DB_NICKNAME.include?(nick)
130
+ raise ArgumentError,
131
+ "nickname '#{nick}' already registered (uuid: #{uuid})"
132
+ end
133
+ DB_NICKNAME[nick] = char_config
134
+ end
135
+ end
136
+
137
+
138
+ end
139
+ end
140
+
141
+ end
142
+ end
@@ -0,0 +1,85 @@
1
+ require 'forwardable'
2
+ module BLE
3
+ # Build information about {https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicsHome.aspx Bluetooth Characteristics}
4
+ #
5
+ class Characteristic
6
+ include CharRegistry
7
+ extend Forwardable
8
+
9
+ # Notify of characteristic not found
10
+ class NotFound < BLE::NotFound
11
+ end
12
+
13
+ def initialize(desc)
14
+ @dbus_obj= desc[:obj]
15
+ @desc= CharDesc.new(desc)
16
+ end
17
+
18
+ def_delegators :@desc, :flag?, :uuid
19
+
20
+ private
21
+ FLAGS = [ 'broadcast',
22
+ 'read',
23
+ 'write-without-response',
24
+ 'write',
25
+ 'notify',
26
+ 'indicate',
27
+ 'authenticated-signed-writes',
28
+ 'reliable-write',
29
+ 'writable-auxiliaries',
30
+ 'encrypt-read',
31
+ 'encrypt-write',
32
+ 'encrypt-authenticated-read',
33
+ 'encrypt-authenticated-write' ]
34
+
35
+
36
+ #++++++++++++++++++++++++++++
37
+ public
38
+ #++++++++++++++++++++++++++++
39
+
40
+ def write(val, raw: false)
41
+ val= _serialize_value(val, raw: raw)
42
+ @dbus_obj[I_GATT_CHARACTERISTIC].WriteValue(val)
43
+ end
44
+
45
+ def read(raw: false)
46
+ val= @dbus_obj[I_GATT_CHARACTERISTIC].ReadValue().first
47
+ val= _deserialize_value(val, raw: raw)
48
+ end
49
+
50
+ # Register to this characteristic for notifications when
51
+ # its value changes.
52
+ def notify!
53
+ @dbus_obj[I_GATT_CHARACTERISTIC].StartNotify
54
+ end
55
+
56
+ def on_change(raw: false, &callback)
57
+ @dbus_obj[I_PROPERTIES].on_signal('PropertiesChanged') do |intf, props|
58
+ case intf
59
+ when I_GATT_CHARACTERISTIC
60
+ val= _deserialize_value(props['Value'], raw: raw)
61
+ callback.call(val)
62
+ end
63
+ end
64
+ end
65
+ #----------------------------
66
+ private
67
+ #----------------------------
68
+
69
+ # Convert Arrays of bytes returned by DBus to Strings of bytes.
70
+ def _serialize_value(val, raw: false)
71
+ if !raw && @desc.write_processors?
72
+ val= @desc.pre_process(val)
73
+ end
74
+ val.unpack('C*')
75
+ end
76
+
77
+ # Convert Arrays of bytes returned by DBus to Strings of bytes.
78
+ def _deserialize_value(val, raw: false)
79
+ val = val.pack('C*')
80
+ val = @desc.post_process(val) if !raw && @desc.read_processors?
81
+ val
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,50 @@
1
+ module BLE
2
+ module Service
3
+ add 'ee0c2080-8786-40ba-ab96-99b91ac981d8',
4
+ name: 'Eddystone-URL Beacon Configuration'
5
+ end
6
+ end
7
+
8
+ module BLE
9
+ class Characteristic
10
+ add 'ee0c2081-8786-40ba-ab96-99b91ac981d8',
11
+ name: 'Eddystone Lock State',
12
+ nick: :esurl_lockstate
13
+
14
+ add 'ee0c2082-8786-40ba-ab96-99b91ac981d8',
15
+ name: 'Eddystone Lock',
16
+ nick: :esurl_lock
17
+
18
+ add 'ee0c2083-8786-40ba-ab96-99b91ac981d8',
19
+ name: 'Eddystone Unlock',
20
+ nick: :esurl_unlock
21
+
22
+ add 'ee0c2084-8786-40ba-ab96-99b91ac981d8',
23
+ name: 'Eddystone URL Data',
24
+ nick: :esurl_data
25
+
26
+ add 'ee0c2085-8786-40ba-ab96-99b91ac981d8',
27
+ name: 'Eddystone Flags',
28
+ nick: :esurl_flags
29
+
30
+ add 'ee0c2086-8786-40ba-ab96-99b91ac981d8',
31
+ name: 'Eddystone Adv Tx Power Levels',
32
+ nick: :esurl_adv_txpower_levels
33
+
34
+ add 'ee0c2087-8786-40ba-ab96-99b91ac981d8',
35
+ name: 'Eddystone TX power mode',
36
+ nick: :esurl_txpower_mode
37
+
38
+ add 'ee0c2088-8786-40ba-ab96-99b91ac981d8',
39
+ name: 'Eddystone Beacon period',
40
+ nick: :esurl_beacon_period
41
+
42
+ add 'ee0c2089-8786-40ba-ab96-99b91ac981d8',
43
+ name: 'Eddystone Reset',
44
+ nick: :esurl_reset
45
+
46
+ add 'ee0c208a-8786-40ba-ab96-99b91ac981d8',
47
+ name: 'Eddystone Radio Tx Power Levels',
48
+ nick: :esurl_radio_txpower_levels
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ module BLE
2
+ module Service
3
+ add '00001530-1212-efde-1523-785feabcd123',
4
+ name: 'Nordic Device Firmware Update Service'
5
+
6
+ add '6e400001-b5a3-f393-e0a9-e50e24dcca9e',
7
+ name: 'Nordic UART Service'
8
+ end
9
+ end
10
+
11
+
12
+ module BLE
13
+ class Characteristic
14
+ add '00001531-1212-efde-1523-785feabcd123', # WriteWithoutResponse
15
+ name: 'DFU Packet'
16
+
17
+ add '00001532-1212-efde-1523-785feabcd123', # Write,Notify
18
+ name: 'DFU Control Point'
19
+
20
+ add '6e400002-b5a3-f393-e0a9-e50e24dcca9e',
21
+ name: 'UART TX'
22
+
23
+ add '6e400003-b5a3-f393-e0a9-e50e24dcca9e',
24
+ name: 'UART RX'
25
+ end
26
+ end
@@ -0,0 +1,142 @@
1
+ module BLE
2
+ class Characteristic
3
+ # C | Integer | 8-bit unsigned (unsigned char)
4
+ # S | Integer | 16-bit unsigned, native endian (uint16_t)
5
+ # L | Integer | 32-bit unsigned, native endian (uint32_t)
6
+ # Q | Integer | 64-bit unsigned, native endian (uint64_t)
7
+ # | |
8
+ # c | Integer | 8-bit signed (signed char)
9
+ # s | Integer | 16-bit signed, native endian (int16_t)
10
+ # l | Integer | 32-bit signed, native endian (int32_t)
11
+ # q | Integer | 64-bit signed, native endian (int64_t)
12
+ #
13
+ # < | Little endian
14
+ # > | Big endian
15
+
16
+ add 0x2A07,
17
+ name: 'Tx Power Level',
18
+ type: 'org.bluetooth.characteristic.tx_power_level',
19
+ vrfy: ->(x) { (-100..20).include?(c) },
20
+ in: ->(s) { s.unpack('c').first },
21
+ out: ->(v) { [ v ].pack('c') }
22
+
23
+ add 0x2A23,
24
+ name: 'System ID',
25
+ type: 'org.bluetooth.characteristic.system_id',
26
+ vrfy: ->(x) { (0..1099511627775).include?(x[0]) &&
27
+ (0..16777215 ).include?(x[1])},
28
+ in: ->(s) { raise NotYetImplemented },
29
+ out: ->(v) { raise NotYetImplemented }
30
+
31
+ add 0x2A24,
32
+ name: 'Model Number String',
33
+ type: 'org.bluetooth.characteristic.model_number_string',
34
+ in: ->(s) { s.force_encoding('UTF-8') },
35
+ out: ->(v) { v.encode('UTF-8') }
36
+
37
+ add 0x2A25,
38
+ name: 'Serial Number String',
39
+ type: 'org.bluetooth.characteristic.serial_number_string',
40
+ in: ->(s) { s.force_encoding('UTF-8') },
41
+ out: ->(v) { v.encode('UTF-8') }
42
+
43
+ add 0x2A26,
44
+ name: 'Firmware Revision String',
45
+ type: 'org.bluetooth.characteristic.firmware_revision_string',
46
+ in: ->(s) { s.force_encoding('UTF-8') },
47
+ out: ->(v) { v.encode('UTF-8') }
48
+
49
+ add 0x2A27,
50
+ name: 'Hardware Revision String',
51
+ type: 'org.bluetooth.characteristic.hardware_revision_string',
52
+ in: ->(s) { s.force_encoding('UTF-8') },
53
+ out: ->(v) { v.encode('UTF-8') }
54
+
55
+ add 0x2A28,
56
+ name: 'Software Revision String',
57
+ type: 'org.bluetooth.characteristic.software_revision_string',
58
+ in: ->(s) { s.force_encoding('UTF-8') },
59
+ out: ->(v) { v.encode('UTF-8') }
60
+
61
+ add 0x2A29,
62
+ name: 'Manufacturer Name String',
63
+ type: 'org.bluetooth.characteristic.manufacturer_name_string',
64
+ in: ->(s) { s.force_encoding('UTF-8') },
65
+ out: ->(v) { v.encode('UTF-8') }
66
+
67
+ add 0x2A2A,
68
+ name: 'IEEE 11073-20601 Regulatory Certification Data List',
69
+ type: 'org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list',
70
+ in: ->(s) { raise NotYetImplemented },
71
+ out: ->(v) { raise NotYetImplemented }
72
+
73
+ add 0x2A50,
74
+ name: 'PnP ID',
75
+ type: 'org.bluetooth.characteristic.pnp_id',
76
+ in: ->(s) { vendor_src, vendor_id,
77
+ product_id, product_version = s.unpack('CS<S<S<')
78
+ vendor_src = case vendor_src
79
+ when 1 then :bluetooth_sig
80
+ when 2 then :usb_forum
81
+ else :reserved
82
+ end
83
+ [ vendor_src, vendor_id,
84
+ product_id, product_version ] },
85
+ out: ->(v) { raise NotYetImplemented }
86
+
87
+
88
+ add 0x2A6E,
89
+ name: 'Temperature',
90
+ type: 'org.bluetooth.characteristic.temperature',
91
+ vrfy: ->(x) { (0..100).include?(x) },
92
+ in: ->(s) { s.unpack('s<').first.to_f / 100 },
93
+ out: ->(v) { [ v*100 ].pack('s<') }
94
+
95
+ add 0x2A76,
96
+ name: 'UV Index',
97
+ type: 'org.bluetooth.characteristic.uv_index',
98
+ in: ->(s) { s.unpack('C').first },
99
+ out: ->(v) { [ v ].pack('C') }
100
+
101
+ add 0x2A77,
102
+ name: 'Irradiance',
103
+ type: 'org.bluetooth.characteristic.irradiance',
104
+ in: ->(s) { s.unpack('S<').first.to_f / 10 },
105
+ out: ->(v) { [ v*10 ].pack('S<') }
106
+
107
+ add 0x2A7A,
108
+ name: 'Heat Index',
109
+ type: 'org.bluetooth.characteristic.heat_index',
110
+ in: ->(s) { s.unpack('c').first },
111
+ out: ->(v) { [ v ].pack('c') }
112
+
113
+ add 0x2A19,
114
+ name: 'Battery Level',
115
+ type: 'org.bluetooth.characteristic.battery_level',
116
+ vrfy: ->(x) { (0..100).include?(x) },
117
+ in: ->(s) { s.unpack('C').first },
118
+ out: ->(v) { [ v ].pack('C') }
119
+
120
+ add 0x2A6F,
121
+ name: 'Humidity',
122
+ type: 'org.bluetooth.characteristic.humidity',
123
+ vrfy: ->(x) { (0..100).include?(x) },
124
+ in: ->(s) { s.unpack('S<').first.to_f / 100 },
125
+ out: ->(v) { [ v*100 ].pack('S<') }
126
+
127
+ add 0x2A6D,
128
+ name: 'Pressure',
129
+ type: 'org.bluetooth.characteristic.pressure',
130
+ vrfy: ->(x) { x >= 0 },
131
+ in: ->(s) { s.unpack('L<').first.to_f / 10 },
132
+ out: ->(v) { [ v*10 ].pack('L<') }
133
+
134
+ add 0x2AB3,
135
+ name: 'Altitude',
136
+ type: 'org.bluetooth.characteristic.altitude',
137
+ vrfy: ->(x) { x >= 0 },
138
+ in: ->(s) { s.unpack('S<').first },
139
+ out: ->(v) { [ v ].pack('S<') }
140
+
141
+ end
142
+ end