TinyCTS
Bluetooth Current Time Service client and server for Arduino
Loading...
Searching...
No Matches
TinyCTSBlueZ.h
Go to the documentation of this file.
1
7
8#pragma once
9#include <gio/gio.h>
10
11#include <algorithm>
12#include <chrono>
13#include <cstdint>
14#include <string>
15#include <vector>
16
17#include "../TinyCTSTypes.h"
18
19namespace tiny_cts {
20
27
29 static bool& verbose() {
30 static bool v = false;
31 return v;
32 }
33
35 static std::string fullUuid(uint16_t uuid16) {
36 char buf[40];
37 snprintf(buf, sizeof buf, "0000%04x-0000-1000-8000-00805f9b34fb", uuid16);
38 return buf;
39 }
40
42 static GDBusConnection* bus(std::string& error) {
43 static GDBusConnection* conn = nullptr;
44 if (conn == nullptr) {
45 GError* err = nullptr;
46 conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &err);
47 if (conn == nullptr) {
48 error = std::string("cannot connect to the system bus: ") + err->message;
49 g_error_free(err);
50 }
51 }
52 return conn;
53 }
54
56 static GVariant* call(const std::string& path, const char* iface, const char* method,
57 GVariant* params, std::string& error, int timeoutMs = 20000) {
58 GDBusConnection* conn = bus(error);
59 if (conn == nullptr) {
60 if (params != nullptr && g_variant_is_floating(params)) g_variant_unref(params);
61 return nullptr;
62 }
63 GError* err = nullptr;
64 GVariant* result = g_dbus_connection_call_sync(
65 conn, "org.bluez", path.c_str(), iface, method, params, nullptr,
66 G_DBUS_CALL_FLAGS_NONE, timeoutMs, nullptr, &err);
67 if (result == nullptr) {
68 g_dbus_error_strip_remote_error(err);
69 error = err->message;
70 if (verbose()) fprintf(stderr, "%s.%s failed: %s\n", iface, method, err->message);
71 g_error_free(err);
72 } else if (verbose()) {
73 fprintf(stderr, "%s.%s ok\n", iface, method);
74 }
75 return result;
76 }
77
79 struct Device {
80 std::string path, address, name;
81 bool cts = false;
82 bool seen = false;
83 bool resolved = false;
84 bool connected = false;
85 int rssi = 0;
86 };
87
89 static std::string findAdapter(const std::string& name, std::string& error) {
90 GVariant* objs = call("/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects",
91 nullptr, error);
92 if (objs == nullptr) {
93 error = "cannot talk to BlueZ: " + error + " (is the bluetooth service running?)";
94 return "";
95 }
96 std::string wanted = "/org/bluez/" + name;
97 std::string found;
98 GVariantIter* it;
99 g_variant_get(objs, "(a{oa{sa{sv}}})", &it);
100 const char* path;
101 GVariant* ifaces;
102 while (g_variant_iter_next(it, "{&o@a{sa{sv}}}", &path, &ifaces)) {
103 GVariant* a = g_variant_lookup_value(ifaces, "org.bluez.Adapter1", nullptr);
104 if (a != nullptr) {
105 if (wanted == path) found = path;
106 g_variant_unref(a);
107 }
108 g_variant_unref(ifaces);
109 }
110 g_variant_iter_free(it);
111 g_variant_unref(objs);
112 if (found.empty()) error = "no Bluetooth adapter " + name + " found";
113 return found;
114 }
115
116 static std::vector<Device> listDevices(const std::string& adapter) {
117 std::vector<Device> devices;
118 std::string error;
119 GVariant* objs = call("/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects",
120 nullptr, error);
121 if (objs == nullptr) return devices;
122 std::string cts = fullUuid(CTS_SERVICE_UUID);
123 GVariantIter* it;
124 g_variant_get(objs, "(a{oa{sa{sv}}})", &it);
125 const char* path;
126 GVariant* ifaces;
127 while (g_variant_iter_next(it, "{&o@a{sa{sv}}}", &path, &ifaces)) {
128 GVariant* p = g_variant_lookup_value(ifaces, "org.bluez.Device1", G_VARIANT_TYPE("a{sv}"));
129 if (p != nullptr && g_str_has_prefix(path, (adapter + "/").c_str())) {
130 Device d;
131 d.path = path;
132 const char* s;
133 if (g_variant_lookup(p, "Address", "&s", &s)) d.address = s;
134 if (g_variant_lookup(p, "Alias", "&s", &s)) d.name = s;
135 if (g_variant_lookup(p, "Name", "&s", &s)) d.name = s;
136 gint16 rssi;
137 if (g_variant_lookup(p, "RSSI", "n", &rssi)) {
138 d.seen = true;
139 d.rssi = rssi;
140 }
141 gboolean b;
142 if (g_variant_lookup(p, "ServicesResolved", "b", &b)) d.resolved = b;
143 if (g_variant_lookup(p, "Connected", "b", &b)) d.connected = b;
144 GVariant* uuids = g_variant_lookup_value(p, "UUIDs", G_VARIANT_TYPE("as"));
145 if (uuids != nullptr) {
146 gsize n;
147 const gchar** list = g_variant_get_strv(uuids, &n);
148 for (gsize i = 0; i < n; i++) {
149 if (cts == list[i]) d.cts = true;
150 }
151 g_free(list);
152 g_variant_unref(uuids);
153 }
154 devices.push_back(d);
155 }
156 if (p != nullptr) g_variant_unref(p);
157 g_variant_unref(ifaces);
158 }
159 g_variant_iter_free(it);
160 g_variant_unref(objs);
161 return devices;
162 }
163
165 static std::string findCharacteristic(const std::string& devicePath, uint16_t uuid16) {
166 std::string result, error;
167 GVariant* objs = call("/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects",
168 nullptr, error);
169 if (objs == nullptr) return result;
170 std::string uuid = fullUuid(uuid16);
171 GVariantIter* it;
172 g_variant_get(objs, "(a{oa{sa{sv}}})", &it);
173 const char* path;
174 GVariant* ifaces;
175 while (g_variant_iter_next(it, "{&o@a{sa{sv}}}", &path, &ifaces)) {
176 GVariant* p = g_variant_lookup_value(ifaces, "org.bluez.GattCharacteristic1",
177 G_VARIANT_TYPE("a{sv}"));
178 if (p != nullptr) {
179 const char* s;
180 if (g_str_has_prefix(path, (devicePath + "/").c_str()) &&
181 g_variant_lookup(p, "UUID", "&s", &s) && uuid == s) {
182 result = path;
183 }
184 g_variant_unref(p);
185 }
186 g_variant_unref(ifaces);
187 }
188 g_variant_iter_free(it);
189 g_variant_unref(objs);
190 return result;
191 }
192
193 static bool readValue(const std::string& characteristic, std::vector<uint8_t>& out,
194 std::string& error) {
195 GVariant* r = call(characteristic, "org.bluez.GattCharacteristic1", "ReadValue",
196 g_variant_new("(a{sv})", nullptr), error);
197 if (r == nullptr) return false;
198 GVariant* bytes = g_variant_get_child_value(r, 0);
199 gsize n;
200 const uint8_t* data = static_cast<const uint8_t*>(g_variant_get_fixed_array(bytes, &n, 1));
201 out.assign(data, data + n);
202 g_variant_unref(bytes);
203 g_variant_unref(r);
204 return true;
205 }
206
207 static bool writeValue(const std::string& characteristic, const uint8_t* data, size_t len,
208 std::string& error) {
209 GVariant* bytes = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, data, len, 1);
210 GVariant* r = call(characteristic, "org.bluez.GattCharacteristic1", "WriteValue",
211 g_variant_new("(@aya{sv})", bytes, nullptr), error);
212 if (r == nullptr) return false;
213 g_variant_unref(r);
214 return true;
215 }
216
217 static bool equalsIgnoreCase(const std::string& a, const std::string& b) {
218 return a.size() == b.size() &&
219 std::equal(a.begin(), a.end(), b.begin(),
220 [](char x, char y) { return tolower(x) == tolower(y); });
221 }
222
224 template <typename Found>
225 static std::vector<Device> discover(const std::string& adapter, uint32_t timeoutMs,
226 Found found, std::string& error) {
227 call(adapter, "org.bluez.Adapter1", "SetDiscoveryFilter",
228 g_variant_new_parsed("({'Transport': <'le'>, 'DuplicateData': <false>},)"), error);
229 bool started =
230 call(adapter, "org.bluez.Adapter1", "StartDiscovery", nullptr, error) != nullptr;
231 if (!started && error.find("InProgress") != std::string::npos) error.clear();
232 std::vector<Device> devices;
233 auto t0 = std::chrono::steady_clock::now();
234 for (;;) {
235 devices = listDevices(adapter);
236 if (found(devices)) break;
237 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
238 std::chrono::steady_clock::now() - t0)
239 .count();
240 if (ms >= timeoutMs) break;
241 g_usleep(250 * 1000);
242 }
243 std::string ignored;
244 if (started) call(adapter, "org.bluez.Adapter1", "StopDiscovery", nullptr, ignored);
245 return devices;
246 }
247
248};
249
250} // namespace tiny_cts
The data of the Bluetooth Current Time Service (CTS, 0x1805): UUIDs and the encoding of the character...
constexpr uint16_t CTS_SERVICE_UUID
16 bit UUID of the Current Time Service
Definition TinyCTSTypes.h:17
A device that BlueZ knows.
Definition TinyCTSBlueZ.h:79
bool cts
the UUIDs include the Current Time Service
Definition TinyCTSBlueZ.h:81
bool resolved
ServicesResolved.
Definition TinyCTSBlueZ.h:83
bool seen
RSSI known: in range during the discovery.
Definition TinyCTSBlueZ.h:82
The functions to talk to BlueZ (all static); the BlueZ client and server use them.
Definition TinyCTSBlueZ.h:26
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 bool & verbose()
Print the D-Bus calls to stderr.
Definition TinyCTSBlueZ.h:29
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