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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
|
/*
* Author: Sven Gothel <sgothel@jausoft.com>
* Copyright (c) 2021 Gothel Software e.K.
* Copyright (c) 2021 ZAFENA AB
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <string>
#include <mutex>
#include <condition_variable>
#include <jau/uuid.hpp>
#include <jau/octets.hpp>
#include <jau/fraction_type.hpp>
#include "BTGattDesc.hpp"
#include "BTGattChar.hpp"
#include "BTGattService.hpp"
#include "HCITypes.hpp"
#ifndef BT_GATT_CMD_HPP_
#define BT_GATT_CMD_HPP_
namespace direct_bt {
/**
* Class maps a GATT command and optionally its asynchronous response
* to a synchronous atomic operation.
*
* The GATT command is issued by writing the associated GATT characteristic value
* via BTGattChar::writeValueNoResp() or BTGattChar::writeValue().
*
* Its optional asynchronous characteristic value notification or indication response
* is awaited and collected after command issuance.
*
* If a response jau::uuid_t is given, notification or indication will be enabled at first send() command
* and disabled at close() or destruction.
*
* @see BTGattChar::writeValueNoResp()
* @see BTGattChar::writeValue()
* @since 2.4.0
*/
class BTGattCmd
{
private:
/** Name, representing the command */
std::string name;
/** Command's BTGattService jau::uuid_t, may be nullptr. */
const jau::uuid_t* service_uuid;
/** Command's BTGattChar value jau::uuid_t to write command, never nullptr. */
const jau::uuid_t* cmd_uuid;
/** Command's optional BTGattChar value jau::uuid_t for the notification or indication response, may be nullptr. */
const jau::uuid_t* rsp_uuid;
std::mutex mtxCommand;
std::mutex mtxRspReceived;
std::condition_variable cvRspReceived;
BTDevice& dev;
jau::POctets rsp_data;
BTGattCharRef cmdCharRef;
BTGattCharRef rspCharRef;
bool setup_done;
class ResponseCharListener : public BTGattCharListener {
private:
BTGattCmd& source;
jau::POctets& rsp_data;
public:
ResponseCharListener(BTGattCmd& source_, jau::POctets& rsp_data_)
: source(source_), rsp_data(rsp_data_)
{ }
void notificationReceived(BTGattCharRef charDecl,
const jau::TROOctets& char_value, const uint64_t timestamp) override;
void indicationReceived(BTGattCharRef charDecl,
const jau::TROOctets& char_value, const uint64_t timestamp,
const bool confirmationSent) override;
};
std::shared_ptr<ResponseCharListener> rspCharListener;
bool verbose;
std::string rspCharStr() const noexcept {
return nullptr != rspCharRef ? rspCharRef->toString() : "n/a";
}
std::string srvUUIDStr() const noexcept {
return nullptr != service_uuid ? service_uuid->toString() : "n/a";
}
std::string rspUUIDStr() const noexcept {
return nullptr != rsp_uuid ? rsp_uuid->toString() : "n/a";
}
bool isConnected() const noexcept;
bool isResolvedEq() const noexcept {
return nullptr != cmdCharRef && cmdCharRef->isValidInstance();
}
HCIStatusCode setup() noexcept;
public:
/**
* Constructor for commands with notification or indication response.
*
* @param dev_ the remote BTDevice
* @param name_ user given name, representing the command
* @param service_uuid_ command's BTGattService jau::uuid_t
* @param cmd_uuid_ command's BTGattChar value jau::uuid_t to write the command
* @param rsp_uuid_ command's BTGattChar value jau::uuid_t for the notification or indication response.
* @param rsp_capacity initial capacity of response sink, see getResponse()
*/
BTGattCmd(BTDevice& dev_, const std::string& name_,
const jau::uuid_t& service_uuid_,
const jau::uuid_t& cmd_uuid_,
const jau::uuid_t& rsp_uuid_,
const jau::nsize_t rsp_capacity) noexcept
: name(name_),
service_uuid(&service_uuid_),
cmd_uuid(&cmd_uuid_),
rsp_uuid(&rsp_uuid_),
dev(dev_),
rsp_data(rsp_capacity, 0 /* size */, jau::endian::little),
cmdCharRef(nullptr),
rspCharRef(nullptr),
setup_done(false),
rspCharListener( std::shared_ptr<ResponseCharListener>( new ResponseCharListener(*this, rsp_data) ) ),
verbose(jau::environment::get().debug)
{ }
/**
* Constructor for commands with notification or indication response.
*
* Since no service UUID is given, the BTGattChar lookup is less efficient.
*
* @param dev_ the remote BTDevice
* @param name_ user given name, representing the command
* @param cmd_uuid_ command's BTGattChar value jau::uuid_t to write the command
* @param rsp_uuid_ command's BTGattChar value jau::uuid_t for the notification or indication response.
* @param rsp_capacity initial capacity of response sink, see getResponse()
*/
BTGattCmd(BTDevice& dev_, const std::string& name_,
const jau::uuid_t& cmd_uuid_,
const jau::uuid_t& rsp_uuid_,
const jau::nsize_t rsp_capacity) noexcept
: name(name_),
service_uuid(nullptr),
cmd_uuid(&cmd_uuid_),
rsp_uuid(&rsp_uuid_),
dev(dev_),
rsp_data(rsp_capacity, 0 /* size */, jau::endian::little),
cmdCharRef(nullptr),
rspCharRef(nullptr),
setup_done(false),
rspCharListener( std::shared_ptr<ResponseCharListener>( new ResponseCharListener(*this, rsp_data) ) ),
verbose(jau::environment::get().debug)
{ }
/**
* Constructor for commands without response.
*
* @param dev_ the remote BTDevice
* @param name_ user given name, representing the command
* @param service_uuid_ command's BTGattService jau::uuid_t
* @param cmd_uuid_ command's BTGattChar value jau::uuid_t to write the command
*/
BTGattCmd(BTDevice& dev_, const std::string& name_,
const jau::uuid_t& service_uuid_,
const jau::uuid_t& cmd_uuid_) noexcept
: name(name_),
service_uuid(&service_uuid_),
cmd_uuid(&cmd_uuid_),
rsp_uuid(nullptr),
dev(dev_),
rsp_data(jau::endian::little),
cmdCharRef(nullptr),
rspCharRef(nullptr),
setup_done(false),
rspCharListener( nullptr ),
verbose(jau::environment::get().debug)
{ }
/**
* Constructor for commands without response.
*
* Since no service UUID is given, the BTGattChar lookup is less efficient.
*
* @param dev_ the remote BTDevice
* @param name_ user given name, representing the command
* @param cmd_uuid_ command's BTGattChar value jau::uuid_t to write the command
*/
BTGattCmd(BTDevice& dev_, const std::string& name_,
const jau::uuid_t& cmd_uuid_) noexcept
: name(name_),
service_uuid(nullptr),
cmd_uuid(&cmd_uuid_),
rsp_uuid(nullptr),
dev(dev_),
rsp_data(jau::endian::little),
cmdCharRef(nullptr),
rspCharRef(nullptr),
setup_done(false),
rspCharListener( nullptr ),
verbose(jau::environment::get().debug)
{ }
/**
* Close this command instance, usually called at destruction.
*
* If a response jau::uuid_t has been given, notification or indication will be disabled.
*/
HCIStatusCode close() noexcept;
~BTGattCmd() noexcept { close(); }
/** Return name, representing the command */
const std::string& getName() const noexcept { return name; }
/** Return command's BTGattService jau::uuid_t, may be nullptr. */
const jau::uuid_t* getServiceUUID() const noexcept { return service_uuid; }
/** Return command's BTGattChar value jau::uuid_t to write command, never nullptr. */
const jau::uuid_t* getCommandUUID() const noexcept { return cmd_uuid; }
/** Return true if a notification or indication response has been set via constructor, otherwise false. */
bool hasResponseSet() const noexcept { return nullptr != rsp_uuid; }
/** Return command's optional BTGattChar value jau::uuid_t for the notification or indication response, may be nullptr. */
const jau::uuid_t* getResponseUUID() const noexcept { return rsp_uuid; }
/** Set verbosity for UUID resolution. */
void setVerbose(const bool v) noexcept { verbose = v; }
/**
* Returns the read-only response data object
* for configured commands with response notification or indication.
*
* jau::TROOctets::size() matches the size of last received command response or zero.
* @see send()
*/
const jau::TROOctets& getResponse() const noexcept { return rsp_data; }
/**
* Query whether all UUIDs of this commands have been resolved.
*
* In case no command has been issued via send() yet,
* the UUIDs will be resolved with this call.
*
* @return true if all UUIDs have been resolved, otherwise false
*/
bool isResolved() noexcept;
/**
* Send the command to the remote BTDevice.
*
* If a notification or indication result jau::uuid_t has been set via constructor,
* it will be awaited and can be retrieved via getResponse() after command returns.
*
* @param prefNoAck pass true to prefer command write without acknowledge, otherwise use with-ack if available
* @param cmd_data raw command octets
* @param timeout maximum duration in fractions of seconds to wait for the response to become available, if any.
* @return
* @see getResponse()
*/
HCIStatusCode send(const bool prefNoAck, const jau::TROOctets& cmd_data, const jau::fraction_i64& timeout) noexcept;
std::string toString() const noexcept;
};
}
#endif // BT_GATT_CMD_HPP_
|