Direct-BT v3.3.0-1-gc2d430c
Direct-BT - Direct Bluetooth Programming.
BTGattCmd.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 * Copyright (c) 2021 ZAFENA AB
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25
26#include "BTGattCmd.hpp"
27
28#include "BTDevice.hpp"
29
30using namespace direct_bt;
31
32void BTGattCmd::ResponseCharListener::store(const jau::TROOctets& char_value) {
33 const jau::nsize_t rsp_pos = rsp_data.size();
34 if( rsp_data.remaining() < char_value.size() ) {
35 rsp_data.recapacity( rsp_pos + char_value.size() );
36 }
37 rsp_data.put_bytes_nc(rsp_pos, char_value.get_ptr(), char_value.size());
38 rsp_data.resize(rsp_pos + char_value.size());
39}
40
41void BTGattCmd::ResponseCharListener::notificationReceived(BTGattCharRef charDecl,
42 const jau::TROOctets& char_value, const uint64_t timestamp) {
43 std::unique_lock<std::mutex> lock(source.mtxRspReceived); // RAII-style acquire and relinquish via destructor
44 DBG_PRINT("BTGattCmd::notificationReceived: Resp %s, value[%s]",
45 charDecl->toString().c_str(), char_value.toString().c_str());
46 store(char_value);
47 if( nullptr != source.dataCallback ) {
48 source.dataCallback(charDecl, char_value, timestamp);
49 }
50 lock.unlock(); // unlock mutex before notify_all to avoid pessimistic re-block of notified wait() thread.
51 source.cvRspReceived.notify_all(); // notify waiting thread
52 (void)timestamp;
53}
54
55void BTGattCmd::ResponseCharListener::indicationReceived(BTGattCharRef charDecl,
56 const jau::TROOctets& char_value, const uint64_t timestamp,
57 const bool confirmationSent)
58{
59 std::unique_lock<std::mutex> lock(source.mtxRspReceived); // RAII-style acquire and relinquish via destructor
60 DBG_PRINT("BTGattCmd::indicationReceived: Resp %s, value[%s]",
61 charDecl->toString().c_str(), char_value.toString().c_str());
62 store(char_value);
63 if( nullptr != source.dataCallback ) {
64 source.dataCallback(charDecl, char_value, timestamp);
65 }
66 lock.unlock(); // unlock mutex before notify_all to avoid pessimistic re-block of notified wait() thread.
67 source.cvRspReceived.notify_all(); // notify waiting thread
68 (void)charDecl;
69 (void)timestamp;
70 (void)confirmationSent;
71}
72
73bool BTGattCmd::isConnected() const noexcept { return dev.getConnected(); }
74
75HCIStatusCode BTGattCmd::setup() noexcept {
76 if( setup_done ) {
78 }
79 setup_done = true;
80 cmdCharRef = nullptr != service_uuid ? dev.findGattChar(*service_uuid, *cmd_uuid)
81 : dev.findGattChar(*cmd_uuid);
82 if( nullptr == cmdCharRef ) {
83 if( verbose ) {
84 jau::INFO_PRINT("Command not found: service %s, char %s",
85 srvUUIDStr().c_str(), cmd_uuid->toString().c_str());
86 }
88 }
89 if( !cmdCharRef->hasProperties(BTGattChar::PropertyBitVal::WriteNoAck) &&
90 !cmdCharRef->hasProperties(BTGattChar::PropertyBitVal::WriteWithAck) ) {
91 if( verbose ) {
92 jau::INFO_PRINT("Command has no write property: %s", cmdCharRef->toString().c_str());
93 }
94 cmdCharRef = nullptr;
96 }
97
98 if( nullptr != rsp_uuid ) {
99 rspCharRef = nullptr != service_uuid ? dev.findGattChar(*service_uuid, *rsp_uuid)
100 : dev.findGattChar(*rsp_uuid);
101 if( nullptr == rspCharRef ) {
102 if( verbose ) {
103 jau::INFO_PRINT("Response not found: service %s, char %s",
104 srvUUIDStr().c_str(), rsp_uuid->toString().c_str());
105 }
106 cmdCharRef = nullptr;
108 }
109 try {
110 bool cccdEnableResult[2];
111 bool cccdRet = rspCharRef->addCharListener( rspCharListener, cccdEnableResult );
112 if( cccdRet ) {
114 } else {
115 if( verbose ) {
116 jau::INFO_PRINT("CCCD Notify/Indicate not supported on response %s", rspCharRef->toString().c_str());
117 }
118 cmdCharRef = nullptr;
119 rspCharRef = nullptr;
121 }
122 } catch ( std::exception & e ) {
123 ERR_PRINT("Exception caught for %s: %s\n", e.what(), toString().c_str());
124 cmdCharRef = nullptr;
125 rspCharRef = nullptr;
127 }
128 } else {
130 }
131}
132
134 std::unique_lock<std::mutex> lockCmd(mtxCommand); // RAII-style acquire and relinquish via destructor
135 const bool wasResolved = isResolvedEq();
136 BTGattCharRef rspCharRefCopy = rspCharRef;
137 cmdCharRef = nullptr;
138 rspCharRef = nullptr;
139 if( !setup_done ) {
141 }
142 setup_done = false;
143 if( !wasResolved ) {
145 }
146 if( !isConnected() ) {
148 }
149 if( nullptr != rspCharRefCopy ) {
150 try {
151 const bool res1 = rspCharRefCopy->removeCharListener(rspCharListener);
152 const bool res2 = rspCharRefCopy->disableIndicationNotification();
153 if( res1 && res2 ) {
155 } else {
157 }
158 } catch ( std::exception & e ) {
159 ERR_PRINT("Exception caught for %s: %s\n", e.what(), toString().c_str());
161 }
162 } else {
164 }
165}
166
167bool BTGattCmd::isResolved() noexcept {
168 std::unique_lock<std::mutex> lockCmd(mtxCommand); // RAII-style acquire and relinquish via destructor
169 if( !setup_done ) {
170 return HCIStatusCode::SUCCESS == setup();
171 } else {
172 return isResolvedEq();
173 }
174}
175
176HCIStatusCode BTGattCmd::send(const bool prefNoAck, const jau::TROOctets& cmd_data, const jau::fraction_i64& timeout) noexcept {
177 return sendImpl(prefNoAck, cmd_data, timeout, true);
178}
179HCIStatusCode BTGattCmd::sendOnly(const bool prefNoAck, const jau::TROOctets& cmd_data) noexcept {
180 return sendImpl(prefNoAck, cmd_data, 0_s, false);
181}
182HCIStatusCode BTGattCmd::sendImpl(const bool prefNoAck, const jau::TROOctets& cmd_data, const jau::fraction_i64& timeout, bool allowResponse) noexcept {
183 std::unique_lock<std::mutex> lockCmd(mtxCommand); // RAII-style acquire and relinquish via destructor
185
186 if( !isConnected() ) {
188 } else {
189 std::unique_lock<std::mutex> lockRsp(mtxRspReceived); // RAII-style acquire and relinquish via destructor
190
191 res = setup();
192 if( HCIStatusCode::SUCCESS != res ) {
193 return res;
194 }
195 rsp_data.resize(0);
196
197 DBG_PRINT("BTGattCmd::sendBlocking: Start: Cmd %s, args[%s], Resp %s, result[%s]",
198 cmdCharRef->toString().c_str(), cmd_data.toString().c_str(),
199 rspCharStr().c_str(), rsp_data.toString().c_str());
200
201 const bool hasWriteNoAck = cmdCharRef->hasProperties(BTGattChar::PropertyBitVal::WriteNoAck);
202 const bool hasWriteWithAck = cmdCharRef->hasProperties(BTGattChar::PropertyBitVal::WriteWithAck);
203 // Prefer WriteNoAck, if hasWriteNoAck and ( prefNoAck -or- !hasWriteWithAck )
204 const bool prefWriteNoAck = hasWriteNoAck && ( prefNoAck || !hasWriteWithAck );
205
206 if( prefWriteNoAck ) {
207 try {
208 if( !cmdCharRef->writeValueNoResp(cmd_data) ) {
209 ERR_PRINT("Write (noAck) to command failed: Cmd %s, args[%s]",
210 cmdCharRef->toString().c_str(), cmd_data.toString().c_str());
212 }
213 } catch ( std::exception & e ) {
214 ERR_PRINT("Exception caught @ Write (noAck) to command failed: Cmd %s, args[%s]: %s",
215 cmdCharRef->toString().c_str(), cmd_data.toString().c_str(), e.what());
217 }
218 } else if( hasWriteWithAck ) {
219 try {
220 if( !cmdCharRef->writeValue(cmd_data) ) {
221 ERR_PRINT("Write (withAck) to command failed: Cmd %s, args[%s]",
222 cmdCharRef->toString().c_str(), cmd_data.toString().c_str());
224 }
225 } catch ( std::exception & e ) {
226 ERR_PRINT("Exception caught @ Write (withAck) to command failed: Cmd %s, args[%s]: %s",
227 cmdCharRef->toString().c_str(), cmd_data.toString().c_str(), e.what());
229 }
230 } else {
231 ERR_PRINT("Command has no write property: %s: %s", cmdCharRef->toString().c_str(), toString().c_str());
233 }
234
235 if( nullptr != rspCharRef && allowResponse ) {
237 while( HCIStatusCode::SUCCESS == res &&
238 ( rspMinSize > rsp_data.size() || ( 0 == rspMinSize && 0 == rsp_data.size() ) )
239 )
240 {
241 if( jau::fractions_i64::zero == timeout ) {
242 cvRspReceived.wait(lockRsp);
243 } else {
244 std::cv_status s = wait_until(cvRspReceived, lockRsp, timeout_time);
245 if( std::cv_status::timeout == s && 0 == rsp_data.size() ) {
246 ERR_PRINT("BTGattCmd::sendBlocking: Timeout: Cmd %s, args[%s]",
247 cmdCharRef->toString().c_str(), cmd_data.toString().c_str());
249 }
250 }
251 }
252 }
253 } // mtxRspReceived
254 if( HCIStatusCode::SUCCESS == res ) {
255 DBG_PRINT("BTGattCmd::sendBlocking: OK: Cmd %s, args[%s], Resp %s, result[%s]",
256 cmdCharRef->toString().c_str(), cmd_data.toString().c_str(),
257 rspCharStr().c_str(), rsp_data.toString().c_str());
258 }
259 return res;
260}
261
262std::string BTGattCmd::toString() const noexcept {
263 return "BTGattCmd["+dev.getName()+":"+name+", service "+srvUUIDStr()+
264 ", char[cmd "+cmd_uuid->toString()+", rsp "+rspUUIDStr()+
265 ", set["+std::to_string(setup_done)+", resolved "+std::to_string(isResolvedEq())+"]]]";
266}
std::string const getName() const noexcept
Returns the remote device name.
Definition: BTDevice.cpp:112
BTGattCharRef findGattChar(const jau::uuid_t &service_uuid, const jau::uuid_t &char_uuid) noexcept
Find a BTGattChar by its service_uuid and char_uuid.
Definition: BTDevice.cpp:2309
bool getConnected() noexcept
Return true if the device has been successfully connected, otherwise false.
Definition: BTDevice.hpp:526
HCIStatusCode close() noexcept
Close this command instance, usually called at destruction.
Definition: BTGattCmd.cpp:133
HCIStatusCode send(const bool prefNoAck, const jau::TROOctets &cmd_data, const jau::fraction_i64 &timeout) noexcept
Send the command to the remote BTDevice.
Definition: BTGattCmd.cpp:176
HCIStatusCode sendOnly(const bool prefNoAck, const jau::TROOctets &cmd_data) noexcept
Send the command to the remote BTDevice, only.
Definition: BTGattCmd.cpp:179
bool isResolved() noexcept
Query whether all UUIDs of this commands have been resolved.
Definition: BTGattCmd.cpp:167
std::string toString() const noexcept
Definition: BTGattCmd.cpp:262
Transient read only and endian aware octet data, i.e.
Definition: octets.hpp:67
std::string toString() const noexcept
Definition: octets.hpp:288
constexpr nsize_t size() const noexcept
Returns the used memory size for read and write operations, may be zero.
Definition: octets.hpp:162
constexpr uint8_t const * get_ptr() const noexcept
Definition: octets.hpp:272
virtual std::string toString() const noexcept=0
Returns the string representation in BE network order, i.e.
#define ERR_PRINT(...)
Use for unconditional error messages, prefix '[elapsed_time] Error @ FILE:LINE FUNC: '.
Definition: debug.hpp:109
#define DBG_PRINT(...)
Use for environment-variable environment::DEBUG conditional debug messages, prefix '[elapsed_time] De...
Definition: debug.hpp:52
std::string to_string(const alphabet &v) noexcept
Definition: base_codec.hpp:97
HCIStatusCode
BT Core Spec v5.2: Vol 1, Part F Controller Error Codes: 1.3 List of Error Codes.
Definition: HCITypes.hpp:138
std::shared_ptr< BTGattChar > BTGattCharRef
Definition: BTGattChar.hpp:410
fraction_timespec getMonotonicTime() noexcept
Returns current monotonic time since Unix Epoch 00:00:00 UTC on 1970-01-01.
Definition: basic_types.cpp:52
uint_fast32_t nsize_t
Natural 'size_t' alternative using uint_fast32_t as its natural sized type.
Definition: int_types.hpp:53
constexpr const jau::fraction_i64 zero(0l, 1lu)
zero is 0/1
void INFO_PRINT(const char *format,...) noexcept
Use for unconditional informal messages, prefix '[elapsed_time] Info: '.
Definition: debug.cpp:248
std::cv_status wait_until(std::condition_variable &cv, std::unique_lock< std::mutex > &lock, const fraction_timespec &absolute_time, const bool monotonic=true) noexcept
wait_until causes the current thread to block until the condition variable is notified,...
Timespec structure using int64_t for its components in analogy to struct timespec_t on 64-bit platfor...
@ WriteNoAck
@ WriteWithAck