aboutsummaryrefslogtreecommitdiff
path: root/src/DBusObject.cpp
blob: c476ea916d902fb2e9929e2ed746362bbb429c13 (plain)
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
// Copyright 2017 Paul Nettle. 
// 
// This file is part of Gobbledegook. 
// 
// Gobbledegook is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
// 
// Gobbledegook is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more details. 
// 
// You should have received a copy of the GNU General Public License 
// along with Gobbledegook.  If not, see <http://www.gnu.org/licenses/>. 
 
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
// 
// >> 
// >>>  INSIDE THIS FILE 
// >> 
// 
// This is an abstraction of a D-Bus object. 
// 
// >> 
// >>>  DISCUSSION 
// >> 
// 
// A D-Bus object is a container for any number of functional interfaces to expose on the bus. Objects are referred to by their 
// path ("/com/acme/widgets"). Here is a simple example of how D-Bus objects relate to Bluetooth services: 
// 
// Object (path)                               Interface (name) 
// 
// /com/acme/widget                            org.bluez.GattService1 
// /com/acme/widget/manufacturer_name          org.bluez.GattCharacteristic1 
// /com/acme/widget/serial_number              org.bluez.GattCharacteristic1 
// 
// In English, this would be read as "The Acme company has a widget, which has two characteristics defining the manufacturer name 
// and serial number for the widget." 
// 
// Finally, we'll include a published flag. Here's what that's all about: 
// 
// BlueZ uses the GetManagedObjects method (from the org.freedesktop.DBus.ObjectManager interface) to interrogate our 
// service(s). Our Server, however, includes all objects and interfaces, including the GetManagedObjects as well as the various 
// interfaces we expose over Bluetooth. Therefore, we'll need a way to know which ones to expose over Bluetooth (which is, in 
// general, everything EXCEPT the object containing the org.freedesktop.DBus.ObjectManager interface.) Since we manage our 
// objects in a hierarchy, only the root object's publish flag matters. 
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
 
#include "GattProperty.h"
#include "DBusInterface.h"
#include "GattService.h"
#include "DBusObject.h"
#include "Utils.h"
#include "GattUuid.h"
#include "Logger.h"
 
// Construct a root object with no parent 
// 
// We'll include a publish flag since only root objects can be published 
DBusObject::DBusObject(const DBusObjectPath &path, bool publish)
: publish(publish), path(path), pParent(nullptr)
{
}
 
// Construct a node object 
// 
// Nodes inherit their parent's publish path 
DBusObject::DBusObject(DBusObject *pParent, const DBusObjectPath &pathElement)
: publish(pParent->publish), path(pathElement), pParent(pParent)
{
}
 
// 
// Accessors 
// 
 
// Returns the `publish` flag 
bool DBusObject::isPublished() const
{
return publish;
}
 
// Returns the path node for this object within the hierarchy 
// 
// This method only returns the node. To get the full path, use `getPath()` 
const DBusObjectPath &DBusObject::getPathNode() const
{
return path;
}
 
// Returns the full path for this object within the hierarchy 
// 
// This method returns the full path. To get the current node, use `getPathNode()` 
DBusObjectPath DBusObject::getPath() const
{
DBusObjectPath path = getPathNode();
const DBusObject *pCurrent = pParent;
 
// Traverse up my chain, adding nodes to the path until we have the full thing 
while(nullptr != pCurrent)
{
path = pCurrent->getPathNode() + path;
pCurrent = pCurrent->pParent;
}
 
return path;
}
 
// Returns the parent object in the hierarchy 
DBusObject &DBusObject::getParent()
{
return *pParent;
}
 
// Returns the list of children objects 
const std::list<DBusObject> &DBusObject::getChildren() const
{
return children;
}
 
// Add a child to this object 
DBusObject &DBusObject::addChild(const DBusObjectPath &pathElement)
{
children.push_back(DBusObject(this, pathElement));
return children.back();
}
 
// Returns a list of interfaces for this object 
const DBusObject::InterfaceList &DBusObject::getInterfaces() const
{
return interfaces;
}
 
// Convenience functions to add a GATT service to the hierarchy 
// 
// We simply add a new child at the given path and add an interface configured as a GATT service to it using the given UUID. 
GattService &DBusObject::gattServiceBegin(const std::string &pathElement, const GattUuid &uuid)
{
DBusObject &child = addChild(DBusObjectPath(pathElement));
GattService &service = *child.addInterface(std::make_shared<GattService>(child, "org.bluez.GattService1"));
service.addProperty<GattService>("UUID", uuid);
service.addProperty<GattService>("Primary"true);
return service;
}
 
// 
// Helpful routines for searching objects 
// 
 
// Finds an interface by name within this D-Bus object 
std::shared_ptr<const DBusInterface> DBusObject::findInterface(const DBusObjectPath &path, const std::string &interfaceName, const DBusObjectPath &basePath) const
{
if ((basePath + getPathNode()) == path)
{
for (std::shared_ptr<const DBusInterface> interface : interfaces)
{
if (interfaceName == interface->getName())
{
return interface;
}
}
}
 
for (const DBusObject &child : getChildren())
{
std::shared_ptr<const DBusInterface> pInterface = child.findInterface(path, interfaceName, basePath + getPathNode());
if (nullptr != pInterface)
{
return pInterface;
}
}
 
return nullptr;
}
 
// Finds a BlueZ method by name within the specified D-Bus interface 
bool DBusObject::callMethod(const DBusObjectPath &path, const std::string &interfaceName, const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData, const DBusObjectPath &basePath) const
{
if ((basePath + getPathNode()) == path)
{
for (std::shared_ptr<const DBusInterface> interface : interfaces)
{
if (interfaceName == interface->getName())
{
if (interface->callMethod(methodName, pConnection, pParameters, pInvocation, pUserData))
{
return true;
}
}
}
}
 
for (const DBusObject &child : getChildren())
{
if (child.callMethod(path, interfaceName, methodName, pConnection, pParameters, pInvocation, pUserData, basePath + getPathNode()))
{
return true;
}
}
 
return false;
}
 
// Periodic timer tick propagation 
void DBusObject::tickEvents(GDBusConnection *pConnection, void *pUserData) const
{
for (std::shared_ptr<const DBusInterface> interface : interfaces)
{
interface->tickEvents(pConnection, pUserData);
}
 
for (const DBusObject &child : getChildren())
{
child.tickEvents(pConnection, pUserData);
}
}
 
// --------------------------------------------------------------------------------------------------------------------------------- 
// XML generation for a D-Bus introspection 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Internal method used to generate introspection XML used to describe our services on D-Bus 
std::string DBusObject::generateIntrospectionXML(int depth) const
{
std::string prefix;
prefix.insert(0, depth * 2' ');
 
std::string xml = std::string();
 
if (depth == 0)
{
xml += "<?xml version='1.0'?>\n";
xml += "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN' 'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>\n";
}
 
xml += prefix + "<node name='" + getPathNode().toString() + "'>\n";
xml += prefix + "  <annotation name='" + kServerOwnedName + ".DBusObject.path' value='" + getPath().toString() + "' />\n";
 
for (std::shared_ptr<const DBusInterface> interface : interfaces)
{
xml += interface->generateIntrospectionXML(depth + 1);
}
 
for (DBusObject child : getChildren())
{
xml += child.generateIntrospectionXML(depth + 1);
}
 
xml += prefix + "</node>\n";
 
if (depth == 0)
{
Logger::debug("Generated XML:");
Logger::debug(xml);
}
 
return xml;
}
 
// --------------------------------------------------------------------------------------------------------------------------------- 
// D-Bus signals 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Emits a signal on the bus from the given path, interface name and signal name, containing a GVariant set of parameters 
void DBusObject::emitSignal(GDBusConnection *pBusConnection, const std::string &interfaceName, const std::string &signalName, GVariant *pParameters)
{
GError *pError = nullptr;
gboolean result = g_dbus_connection_emit_signal
(
pBusConnection,          // GDBusConnection *connection 
NULL,                    // const gchar *destination_bus_name 
getPath().c_str(),       // const gchar *object_path 
interfaceName.c_str(),   // const gchar *interface_name 
signalName.c_str(),      // const gchar *signal_name 
pParameters,             // GVariant *parameters 
&pError                  // GError **error 
);
 
if (0 == result)
{
Logger::error(SSTR << "Failed to emit signal named '" << signalName << "': " << (nullptr == pError ? "Unknown" : pError->message));
}
}