sb-ble 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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