TinyCTS
Bluetooth Current Time Service client and server for Arduino
Loading...
Searching...
No Matches
TinyCTSBlueZBackend.h
Go to the documentation of this file.
1
5
6#pragma once
7#include <cstring>
8
9#include "TinyCTSBlueZ.h"
10#include "TinyCTSDesktop.h"
11
12namespace tiny_cts {
13
20class TinyCTSBlueZCentral : public TinyCTSCentral {
21 public:
22 explicit TinyCTSBlueZCentral(const std::string& adapter) : _adapterName(adapter) {}
23 ~TinyCTSBlueZCentral() override { disconnect(); }
24
25 bool scan(uint32_t timeoutMs, std::vector<TinyCTSScanEntry>& result,
26 std::string& error) override {
27 if (!open(error)) return false;
28 std::vector<TinyCTSBlueZ::Device> devices = TinyCTSBlueZ::discover(
29 _adapter, timeoutMs, [](const std::vector<TinyCTSBlueZ::Device>&) { return false; }, error);
30 for (const TinyCTSBlueZ::Device& d : devices) {
31 if (d.seen) result.push_back(entry(d));
32 }
33 return true;
34 }
35
36 bool connect(const std::string& wanted, uint32_t timeoutMs, TinyCTSScanEntry& found,
37 std::string& error) override {
38 disconnect();
39 if (!open(error)) return false;
40 std::vector<TinyCTSBlueZ::Device> devices = TinyCTSBlueZ::discover(
41 _adapter, timeoutMs,
42 [&](const std::vector<TinyCTSBlueZ::Device>& l) { return pick(l, wanted) != nullptr; }, error);
43 const TinyCTSBlueZ::Device* d = pick(devices, wanted);
44 if (d == nullptr) {
45 error = wanted.empty() ? "no device with the Current Time Service found"
46 : "device " + wanted + " not found";
47 return false;
48 }
49 _device = *d;
50 if (!_device.connected &&
51 TinyCTSBlueZ::call(_device.path, "org.bluez.Device1", "Connect", nullptr, error, 30000) ==
52 nullptr) {
53 error = "cannot connect to " + _device.address + ": " + error;
54 _device = TinyCTSBlueZ::Device();
55 return false;
56 }
57 for (int i = 0; i < 100; i++) { // up to 10 s
58 for (const TinyCTSBlueZ::Device& l : TinyCTSBlueZ::listDevices(_adapter)) {
59 if (l.path == _device.path && l.resolved) {
60 found = entry(_device);
61 return true;
62 }
63 }
64 g_usleep(100 * 1000);
65 }
66 error = "no services from " + _device.address;
67 disconnect();
68 return false;
69 }
70
71 bool read(uint16_t uuid, std::vector<uint8_t>& value, std::string& error) override {
72 std::string path = TinyCTSBlueZ::findCharacteristic(_device.path, uuid);
73 if (path.empty()) {
74 error = "the device does not have the characteristic";
75 return false;
76 }
77 return TinyCTSBlueZ::readValue(path, value, error);
78 }
79
80 bool write(uint16_t uuid, const uint8_t* data, size_t len, std::string& error) override {
81 std::string path = TinyCTSBlueZ::findCharacteristic(_device.path, uuid);
82 if (path.empty()) {
83 error = "the device does not have the characteristic";
84 return false;
85 }
86 return TinyCTSBlueZ::writeValue(path, data, len, error);
87 }
88
89 void disconnect() override {
90 if (_device.path.empty()) return;
91 std::string ignored;
92 TinyCTSBlueZ::call(_device.path, "org.bluez.Device1", "Disconnect", nullptr, ignored);
93 _device = TinyCTSBlueZ::Device();
94 }
95
96 private:
97 bool open(std::string& error) {
98 error.clear();
99 _adapter = TinyCTSBlueZ::findAdapter(_adapterName, error);
100 return !_adapter.empty();
101 }
102
103 static TinyCTSScanEntry entry(const TinyCTSBlueZ::Device& d) {
104 TinyCTSScanEntry e;
105 e.address = d.address;
106 e.name = d.name;
107 e.rssi = d.rssi;
108 e.hasCts = d.cts;
109 return e;
110 }
111
112 static const TinyCTSBlueZ::Device* pick(const std::vector<TinyCTSBlueZ::Device>& devices,
113 const std::string& wanted) {
114 for (const TinyCTSBlueZ::Device& d : devices) {
115 if (wanted.empty()) {
116 if (d.cts && d.seen) return &d;
117 } else if (TinyCTSBlueZ::equalsIgnoreCase(d.address, wanted) || d.name == wanted) {
118 if (d.seen || d.connected) return &d;
119 }
120 }
121 return nullptr;
122 }
123
124 std::string _adapterName, _adapter;
125 TinyCTSBlueZ::Device _device;
126};
127
135class TinyCTSBlueZPeripheral : public TinyCTSPeripheral {
136 public:
137 explicit TinyCTSBlueZPeripheral(const std::string& adapter) : _adapterName(adapter) {}
138 ~TinyCTSBlueZPeripheral() override { stop(); }
139
140 bool start(const std::string& name, bool settable, bool encrypted,
141 TinyCTSPeripheralHandler* handler, std::string& error) override {
142 _handler = handler;
143 _name = name;
144 _settable = settable;
145 _encrypted = encrypted;
146 _error.clear();
147 _adapter = TinyCTSBlueZ::findAdapter(_adapterName, _error);
148 GDBusConnection* conn = _adapter.empty() ? nullptr : TinyCTSBlueZ::bus(_error);
149 if (conn == nullptr) {
150 error = _error;
151 return false;
152 }
153
154 _nodeInfo = g_dbus_node_info_new_for_xml(xml(), nullptr);
155 _chars[0] = {std::string(Service) + "/char0", CTS_CURRENT_TIME_UUID, true};
156 _chars[1] = {std::string(Service) + "/char1", CTS_LOCAL_TIME_INFO_UUID, false};
157 _chars[2] = {std::string(Service) + "/char2", CTS_REFERENCE_TIME_INFO_UUID, false};
158 if (!exportObject(App, "org.freedesktop.DBus.ObjectManager") ||
159 !exportObject(Service, "org.bluez.GattService1") ||
160 !exportObject(_chars[0].path.c_str(), "org.bluez.GattCharacteristic1") ||
161 !exportObject(_chars[1].path.c_str(), "org.bluez.GattCharacteristic1") ||
162 !exportObject(_chars[2].path.c_str(), "org.bluez.GattCharacteristic1") ||
163 !exportObject(Advertisement, "org.bluez.LEAdvertisement1")) {
164 error = _error;
165 stop();
166 return false;
167 }
168 _started = true;
169
170 // BlueZ calls back while it registers: the main loop has to run
171 _pending = 2;
172 g_dbus_connection_call(conn, "org.bluez", _adapter.c_str(), "org.bluez.GattManager1",
173 "RegisterApplication", g_variant_new_parsed("(%o, @a{sv} {})", App),
174 nullptr, G_DBUS_CALL_FLAGS_NONE, 20000, nullptr, onRegistered, this);
175 g_dbus_connection_call(conn, "org.bluez", _adapter.c_str(),
176 "org.bluez.LEAdvertisingManager1", "RegisterAdvertisement",
177 g_variant_new_parsed("(%o, @a{sv} {})", Advertisement), nullptr,
178 G_DBUS_CALL_FLAGS_NONE, 20000, nullptr, onRegistered, this);
179 for (int i = 0; i < 4000 && _pending > 0; i++) { // up to 20 s
180 process();
181 g_usleep(5 * 1000);
182 }
183 if (_pending > 0 && _error.empty()) _error = "BlueZ does not answer";
184 if (!_error.empty()) {
185 error = _error;
186 stop();
187 return false;
188 }
189 _registered = true;
190 return true;
191 }
192
193 void process() override {
194 while (g_main_context_iteration(nullptr, FALSE)) {
195 }
196 }
197
198 void notify(uint16_t uuid, const std::vector<uint8_t>& value) override {
199 if (!_notifying) return;
200 for (const Chr& c : _chars) {
201 if (c.uuid == uuid) emitValue(c, value);
202 }
203 }
204
205 void stop() override {
206 std::string ignored;
207 if (_registered) {
208 TinyCTSBlueZ::call(_adapter, "org.bluez.GattManager1", "UnregisterApplication",
209 g_variant_new("(o)", App), ignored, 2000);
210 TinyCTSBlueZ::call(_adapter, "org.bluez.LEAdvertisingManager1", "UnregisterAdvertisement",
211 g_variant_new("(o)", Advertisement), ignored, 2000);
212 _registered = false;
213 }
214 GDBusConnection* conn = TinyCTSBlueZ::bus(ignored);
215 for (guint id : _ids) {
216 if (conn != nullptr) g_dbus_connection_unregister_object(conn, id);
217 }
218 _ids.clear();
219 if (_nodeInfo != nullptr) g_dbus_node_info_unref(_nodeInfo);
220 _nodeInfo = nullptr;
221 _started = false;
222 _notifying = false;
223 }
224
225 private:
226 static constexpr const char* App = "/org/tinycts";
227 static constexpr const char* Service = "/org/tinycts/service0";
228 static constexpr const char* Advertisement = "/org/tinycts/adv0";
229
231 struct Chr {
232 std::string path;
233 uint16_t uuid = 0;
234 bool current = false;
235 };
236
237 static const char* xml() {
238 return "<node>"
239 " <interface name='org.freedesktop.DBus.ObjectManager'>"
240 " <method name='GetManagedObjects'>"
241 " <arg type='a{oa{sa{sv}}}' name='objects' direction='out'/>"
242 " </method>"
243 " </interface>"
244 " <interface name='org.bluez.GattService1'>"
245 " <property name='UUID' type='s' access='read'/>"
246 " <property name='Primary' type='b' access='read'/>"
247 " </interface>"
248 " <interface name='org.bluez.GattCharacteristic1'>"
249 " <method name='ReadValue'>"
250 " <arg type='a{sv}' name='options' direction='in'/>"
251 " <arg type='ay' name='value' direction='out'/>"
252 " </method>"
253 " <method name='WriteValue'>"
254 " <arg type='ay' name='value' direction='in'/>"
255 " <arg type='a{sv}' name='options' direction='in'/>"
256 " </method>"
257 " <method name='StartNotify'/>"
258 " <method name='StopNotify'/>"
259 " <property name='UUID' type='s' access='read'/>"
260 " <property name='Service' type='o' access='read'/>"
261 " <property name='Flags' type='as' access='read'/>"
262 " <property name='Notifying' type='b' access='read'/>"
263 " </interface>"
264 " <interface name='org.bluez.LEAdvertisement1'>"
265 " <method name='Release'/>"
266 " <property name='Type' type='s' access='read'/>"
267 " <property name='ServiceUUIDs' type='as' access='read'/>"
268 " <property name='LocalName' type='s' access='read'/>"
269 " </interface>"
270 "</node>";
271 }
272
273 bool exportObject(const char* path, const char* iface) {
274 static const GDBusInterfaceVTable vtable = {onCall, onGet, nullptr, {}};
275 GError* err = nullptr;
276 GDBusConnection* conn = TinyCTSBlueZ::bus(_error);
277 GDBusInterfaceInfo* info = g_dbus_node_info_lookup_interface(_nodeInfo, iface);
278 guint id = conn == nullptr ? 0
279 : g_dbus_connection_register_object(conn, path, info, &vtable,
280 this, nullptr, &err);
281 if (id == 0) {
282 if (err != nullptr) {
283 _error = std::string("cannot export ") + path + ": " + err->message;
284 g_error_free(err);
285 }
286 return false;
287 }
288 _ids.push_back(id);
289 return true;
290 }
291
292 static void onRegistered(GObject* source, GAsyncResult* res, gpointer self) {
293 TinyCTSBlueZPeripheral* s = static_cast<TinyCTSBlueZPeripheral*>(self);
294 GError* err = nullptr;
295 GVariant* r = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &err);
296 s->_pending--;
297 if (r == nullptr) {
298 g_dbus_error_strip_remote_error(err);
299 s->_error = std::string("BlueZ refused the registration: ") + err->message;
300 g_error_free(err);
301 return;
302 }
303 g_variant_unref(r);
304 }
305
306 const Chr* find(const char* path) const {
307 for (const Chr& c : _chars) {
308 if (c.path == path) return &c;
309 }
310 return nullptr;
311 }
312
313 static GVariant* bytes(const std::vector<uint8_t>& v) {
314 return g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, v.data(), v.size(), 1);
315 }
316
317 GVariant* flags(const Chr& c) const {
318 GVariantBuilder b;
319 g_variant_builder_init(&b, G_VARIANT_TYPE("as"));
320 g_variant_builder_add(&b, "s", "read");
321 if (c.current) g_variant_builder_add(&b, "s", "notify");
322 if (_settable && c.uuid != CTS_REFERENCE_TIME_INFO_UUID) {
323 g_variant_builder_add(&b, "s", _encrypted ? "encrypt-write" : "write");
324 }
325 return g_variant_builder_end(&b);
326 }
327
328 GVariant* managed() {
329 GVariantBuilder objects;
330 g_variant_builder_init(&objects, G_VARIANT_TYPE("a{oa{sa{sv}}}"));
331 GVariant* service = g_variant_new_parsed(
332 "{'org.bluez.GattService1': {'UUID': <%s>, 'Primary': <true>}}",
333 TinyCTSBlueZ::fullUuid(CTS_SERVICE_UUID).c_str());
334 g_variant_builder_add(&objects, "{o@a{sa{sv}}}", Service, service);
335 for (const Chr& c : _chars) {
336 GVariantBuilder props;
337 g_variant_builder_init(&props, G_VARIANT_TYPE("a{sv}"));
338 g_variant_builder_add(&props, "{sv}", "UUID",
339 g_variant_new_string(TinyCTSBlueZ::fullUuid(c.uuid).c_str()));
340 g_variant_builder_add(&props, "{sv}", "Service", g_variant_new_object_path(Service));
341 g_variant_builder_add(&props, "{sv}", "Flags", flags(c));
342 GVariantBuilder ifaces;
343 g_variant_builder_init(&ifaces, G_VARIANT_TYPE("a{sa{sv}}"));
344 g_variant_builder_add(&ifaces, "{sa{sv}}", "org.bluez.GattCharacteristic1", &props);
345 g_variant_builder_add(&objects, "{oa{sa{sv}}}", c.path.c_str(), &ifaces);
346 }
347 return g_variant_new("(a{oa{sa{sv}}})", &objects);
348 }
349
350 void emitValue(const Chr& c, const std::vector<uint8_t>& value) {
351 std::string ignored;
352 GDBusConnection* conn = TinyCTSBlueZ::bus(ignored);
353 if (conn == nullptr) return;
354 GVariantBuilder changed;
355 g_variant_builder_init(&changed, G_VARIANT_TYPE("a{sv}"));
356 g_variant_builder_add(&changed, "{sv}", "Value", bytes(value));
357 g_dbus_connection_emit_signal(
358 conn, nullptr, c.path.c_str(), "org.freedesktop.DBus.Properties", "PropertiesChanged",
359 g_variant_new("(sa{sv}as)", "org.bluez.GattCharacteristic1", &changed, nullptr),
360 nullptr);
361 }
362
363 static void onCall(GDBusConnection*, const gchar*, const gchar* path, const gchar*,
364 const gchar* method, GVariant* params, GDBusMethodInvocation* inv,
365 gpointer self) {
366 TinyCTSBlueZPeripheral* s = static_cast<TinyCTSBlueZPeripheral*>(self);
367 if (!strcmp(method, "GetManagedObjects")) {
368 g_dbus_method_invocation_return_value(inv, s->managed());
369 } else if (!strcmp(method, "ReadValue")) {
370 const Chr* c = s->find(path);
371 std::vector<uint8_t> value;
372 if (c == nullptr || s->_handler == nullptr || !s->_handler->onRead(c->uuid, value)) {
373 g_dbus_method_invocation_return_dbus_error(inv, "org.bluez.Error.Failed", "unknown");
374 return;
375 }
376 g_dbus_method_invocation_return_value(inv, g_variant_new("(@ay)", bytes(value)));
377 } else if (!strcmp(method, "WriteValue")) {
378 const Chr* c = s->find(path);
379 GVariant* data;
380 GVariant* options;
381 g_variant_get(params, "(@ay@a{sv})", &data, &options);
382 gsize n;
383 const uint8_t* d = static_cast<const uint8_t*>(g_variant_get_fixed_array(data, &n, 1));
384 bool ok = c != nullptr && s->_handler != nullptr && s->_handler->onWrite(c->uuid, d, n);
385 g_variant_unref(data);
386 g_variant_unref(options);
387 if (ok) {
388 g_dbus_method_invocation_return_value(inv, nullptr);
389 } else {
390 g_dbus_method_invocation_return_dbus_error(inv, "org.bluez.Error.NotPermitted",
391 "the time cannot be set");
392 }
393 } else if (!strcmp(method, "StartNotify")) {
394 s->_notifying = true;
395 g_dbus_method_invocation_return_value(inv, nullptr);
396 } else if (!strcmp(method, "StopNotify")) {
397 s->_notifying = false;
398 g_dbus_method_invocation_return_value(inv, nullptr);
399 } else { // Release
400 g_dbus_method_invocation_return_value(inv, nullptr);
401 }
402 }
403
404 static GVariant* onGet(GDBusConnection*, const gchar*, const gchar* path, const gchar* iface,
405 const gchar* prop, GError**, gpointer self) {
406 TinyCTSBlueZPeripheral* s = static_cast<TinyCTSBlueZPeripheral*>(self);
407 if (!strcmp(iface, "org.bluez.GattService1")) {
408 if (!strcmp(prop, "UUID")) {
409 return g_variant_new_string(TinyCTSBlueZ::fullUuid(CTS_SERVICE_UUID).c_str());
410 }
411 return g_variant_new_boolean(TRUE);
412 }
413 if (!strcmp(iface, "org.bluez.LEAdvertisement1")) {
414 if (!strcmp(prop, "Type")) return g_variant_new_string("peripheral");
415 if (!strcmp(prop, "LocalName")) return g_variant_new_string(s->_name.c_str());
416 std::string u = TinyCTSBlueZ::fullUuid(CTS_SERVICE_UUID);
417 const gchar* uuids[] = {u.c_str(), nullptr};
418 return g_variant_new_strv(uuids, 1);
419 }
420 const Chr* c = s->find(path);
421 if (c == nullptr) return nullptr;
422 if (!strcmp(prop, "UUID")) return g_variant_new_string(TinyCTSBlueZ::fullUuid(c->uuid).c_str());
423 if (!strcmp(prop, "Service")) return g_variant_new_object_path(Service);
424 if (!strcmp(prop, "Notifying")) return g_variant_new_boolean(s->_notifying);
425 return s->flags(*c);
426 }
427
428 std::string _adapterName, _adapter, _name, _error;
429 TinyCTSPeripheralHandler* _handler = nullptr;
430 GDBusNodeInfo* _nodeInfo = nullptr;
431 std::vector<guint> _ids;
432 Chr _chars[3];
433 int _pending = 0;
434 bool _settable = false, _encrypted = true;
435 bool _started = false, _registered = false, _notifying = false;
436};
437
438inline std::unique_ptr<TinyCTSCentral> ctsCreateCentral(const std::string& adapter) {
439 return std::unique_ptr<TinyCTSCentral>(new TinyCTSBlueZCentral(adapter));
440}
441
442inline std::unique_ptr<TinyCTSPeripheral> ctsCreatePeripheral(const std::string& adapter) {
443 return std::unique_ptr<TinyCTSPeripheral>(new TinyCTSBlueZPeripheral(adapter));
444}
445
446} // namespace tiny_cts
The access to BlueZ, the Bluetooth stack of Linux, over D-Bus (GIO): the functions that the BlueZ cli...
std::unique_ptr< TinyCTSPeripheral > ctsCreatePeripheral(const std::string &adapter)
Definition TinyCTSBlueZBackend.h:442
std::unique_ptr< TinyCTSCentral > ctsCreateCentral(const std::string &adapter)
Definition TinyCTSBlueZBackend.h:438
The interface between the desktop classes TinyCTSClient/TinyCTSServer and the Bluetooth stack of the ...
constexpr uint16_t CTS_CURRENT_TIME_UUID
16 bit UUID of the Current Time characteristic (read, notify, write)
Definition TinyCTSTypes.h:19
constexpr uint16_t CTS_REFERENCE_TIME_INFO_UUID
16 bit UUID of the Reference Time Information characteristic (read)
Definition TinyCTSTypes.h:23
constexpr uint16_t CTS_LOCAL_TIME_INFO_UUID
16 bit UUID of the Local Time Information characteristic (read, write)
Definition TinyCTSTypes.h:21
The client side for Linux: scans, connects and reads or writes the characteristics through BlueZ (org...
Definition TinyCTSBlueZBackend.h:20
bool write(uint16_t uuid, const uint8_t *data, size_t len, std::string &error) override
Definition TinyCTSBlueZBackend.h:80
void disconnect() override
Definition TinyCTSBlueZBackend.h:89
bool read(uint16_t uuid, std::vector< uint8_t > &value, std::string &error) override
Definition TinyCTSBlueZBackend.h:71
bool connect(const std::string &wanted, uint32_t timeoutMs, TinyCTSScanEntry &found, std::string &error) override
Definition TinyCTSBlueZBackend.h:36
bool scan(uint32_t timeoutMs, std::vector< TinyCTSScanEntry > &result, std::string &error) override
Definition TinyCTSBlueZBackend.h:25
The server side for Linux: exports the Current Time Service as a GATT application and an advertisemen...
Definition TinyCTSBlueZBackend.h:135
void process() override
Definition TinyCTSBlueZBackend.h:193
void stop() override
Definition TinyCTSBlueZBackend.h:205
void notify(uint16_t uuid, const std::vector< uint8_t > &value) override
Definition TinyCTSBlueZBackend.h:198
bool start(const std::string &name, bool settable, bool encrypted, TinyCTSPeripheralHandler *handler, std::string &error) override
Definition TinyCTSBlueZBackend.h:140
The client side (central role) of the Bluetooth stack of the operating system: finds a device,...
Definition TinyCTSDesktop.h:38
The callbacks of a TinyCTSPeripheral: it asks the handler for the value when a client reads a charact...
Definition TinyCTSDesktop.h:74
The server side (peripheral role) of the Bluetooth stack of the operating system: offers the Current ...
Definition TinyCTSDesktop.h:95
A device that BlueZ knows.
Definition TinyCTSBlueZ.h:79
static std::string findAdapter(const std::string &name, std::string &error)
The object path of the Bluetooth adapter (empty and the message in error if not found).
Definition TinyCTSBlueZ.h:89
static std::vector< Device > discover(const std::string &adapter, uint32_t timeoutMs, Found found, std::string &error)
Look for devices. The scan stops when found(devices) returns true, or at the timeout.
Definition TinyCTSBlueZ.h:225
static GDBusConnection * bus(std::string &error)
The connection to the system bus (nullptr and the message in error if not possible).
Definition TinyCTSBlueZ.h:42
static std::string fullUuid(uint16_t uuid16)
The 128 bit UUID of a 16 bit UUID.
Definition TinyCTSBlueZ.h:35
static std::string findCharacteristic(const std::string &devicePath, uint16_t uuid16)
The object path of a characteristic of a connected device (empty if not found).
Definition TinyCTSBlueZ.h:165
static GVariant * call(const std::string &path, const char *iface, const char *method, GVariant *params, std::string &error, int timeoutMs=20000)
A synchronous call to BlueZ. Returns nullptr and the message in error.
Definition TinyCTSBlueZ.h:56
A device that was found by TinyCTSClient::scan().
Definition TinyCTSDesktop.h:18