forked from edrosten/libblepp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathATT.txt
186 lines (128 loc) · 6.91 KB
/
ATT.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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
174
175
176
177
178
179
180
181
182
183
184
Haskell Notation
----------------
I've used Haskell style notation. Functions, dictionaries and arrays are
essentially similar in that both map an input to an output, i.e. A -> B.
Tuples are indicated with "," and "()". Returned lists or arrays with [].
Alternatives are indicated with |. For arguments, The type is written first, and
the name (if applicable) follows after a "." .
Attributes
----------
The device contains an array of attributes. The index of tha attributes in known
as the handle. The attributes themselves consist of a "type" and a "value". The
type is a type code and is represented as a UUID. UUIDs are either 16 or 128
bits long. 16 bit UUIDs are converted to proper 128 bit UUID by setting the
remaining 112 bits to some fixed values. The purpose of 16 bit UUIDs is to save
on bandwidth.
Every attribute must have a type. The value is just plain old data (a bunch of
bytes) and may be readable or writable[*] or neither. The read/write status of
an attribute is defined by a higher level protocol and there is no way of
querying this.
[*] The data may be also indicatable or notifiable. In these cases, the
bluetooth device (the server) may send the attribute values back to the client
at any time without solicitation.
So, in essence the server has an array of:
[ (Handle, Type, Data | Null)]
Being an array, one can naturally index it using Handle. In some cases however
it is also possible to index it by the Type in some cases.
There are various function for extracting information from the server:
find_information: [(Handle, Type)]
This gets a list of attributes back from the server as the handel and type. The
data can be read from an attribute in one of two ways:
read: Handle->Data | Error
which reads the data from a given handle. Alternatively, data can be read by the
Type:
read_by_type: Type -> [(Handle | Data)]
Since several attributes may share a type (and often do), read_by_type must be
able to return a of attributes. Since the type is known, all it has to give back
is the Handle and the data.
Naturally, there exist commands for writing too.
There is also a concept of Grouping as well. A group is a contiguous set of
attributes (contiguous according to the handle). Groups can be read by their
type. Groups are delimited by attributes of the specified type. A sample set of
attributes, as [(Handle, Type, Data)] follows. Note that the Types and data are
Arbitrary. If the group type is specified to be 0xdead, then thr grouping
appears as:
0001 dead some data \
0002 2a00 xxxxx | First group
0003 5600 xxxxxxxx /_________________
0004 dead more data \
0005 1234 xxxxxxx | Second group
0006 4567 xxxxxxxx |
0007 1231 xxxxx /_________________
0008 dead another datum \ Third group
0009 wtf /
EOF
The command to read this is:
read_by_group_type Type->[(Handle, Handle, Data)]
where, the start and end handle along with the data for the start handle is
returned, so in this case
read_by_group_type(dead) will return:
0001 0003 "some data"
0005 0007 "more data"
0008 ffff "another datum"
ffff being the highest possible handle number indicates that nothing can follow.
It would appear that in the absence of efficiency concerns, one could replicate
all the functions by queryiing every attribute with find_information and then
reading whichever handles are necessary with read.
Actually reading data
---------------------
The BLE packet length is a mere 23 bytes. Optionally both devices may be able to
lengthen it, but this is not normally supported.
In practice each query can only be answered with a very limited amount of data.
Note that each of the functions above (except read) returns a list of handles.
In the protocol, each function actually takes a start and end handle as an
argument which limits which handles will be returned. In order to get a complete
list of things, one must keep setting the start handle to one plus the last
handle returned and reissuing the call. When no handles can be returned an error
message is sent back, indicating that no handles exist in the range.
The read_by_type function is actually more like:
read_by_type: (Type, Handle.first, Handle.last) -> [ (Handle, Data) ] | Error.no_attribute_in_range
The Generic Attribute Profile
-----------------------------
The generic attribute (GATT) profile defines a standard set of attributes,
types and groups along with mechanisms for discovery.
A GATT profile consists of a number of services. A service defines a set of
"characteristics". A characteristic pas properties (e.g. readable, writable),
a value and zero or more descriptors (e.g. some textual description).
Primary services are groups of handles which have the name <<Primary Service>>
aka 0x2800. Therefore
read_group_by_type(0x2800)
returns the list of services. Any handle falling in the the group belongs to
the service named by that group. The names are either ones specified by the SIG
or custom ones.
Within each service there is a list of characteristics. Each characteristic
contains a group of handles. However, it is not possible to use
read_by_group_type on <<Characteristic>> (aka 2803), since characteristic groups
are terminated either by the presence of the next characteristic or by the next
service. Recall that read_by_group_type insists that the delimiter must be the
type.
A characteristic consists of:
* a characteristic definition (Type <<Characteristic>>)
which includes the Type of the characteristic, the handle of the
characteristic value and some miscellaneous flags.
* A characteristic value. Naturally, this has the Type of the characteristic.
The value may not actually be readable, or writable. For example the value
might only be pushed to the client via notifications.
* Additional descriptors. The descriptors contain miscellaneous extra
information about the characteristic. The descriptors have a Type which
declares the semantic meaning of the descriptor. The meaning of the data is
naturally dependent on the Type.
Common descriptors are:
<<Characteristic User Description>> (2901) Some text describing the
characteristic.
<<Client Characteristic Configuration>> (2902) A write only attribute that is
written to enable and disable notifications and indications on thr
characteristic value.
There are others too.
The list of characteristics can be read by reading type <<Characteristic>> aka 2803.
read_by_type 0x2803
The return from this is for each characteristic:
<Property> <Handle> <Type>
Property is a bit field indicating various properties, things like readable,
writable, notifyable, and so on.
Two services must always be present: <<Generic Access>> (aka 1800) and <<Generic
Attribute>> (aka 1801)
If a characteristic value is readable, it is indicated by the property. Also,
the value will have handle <Handle> and type <Type>. It can be read by reading
the handle or reading by it's type. In other words <Handle> and <Type> are
(nearly?) tautological.