// 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 . // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // // >> // >>> INSIDE THIS FILE // >> // // This is our representation of a GATT Characteristic which is intended to be used in our server description // // >> // >>> DISCUSSION // >> // // A GATT characteristic is the component within the Bluetooth LE standard that holds and serves data over Bluetooth. This class // is intended to be used within the server description. For an explanation of how this class is used, see the detailed discussion // in Server.cpp. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #include "GattCharacteristic.h" #include "GattDescriptor.h" #include "GattProperty.h" #include "GattUuid.h" #include "DBusObject.h" #include "GattService.h" #include "Utils.h" #include "Logger.h" namespace ggk { // // Standard constructor // // Construct a GattCharacteristic // // Genreally speaking, these objects should not be constructed directly. Rather, use the `gattCharacteristicBegin()` method // in `GattService`. GattCharacteristic::GattCharacteristic(DBusObject &owner, GattService &service, const std::string &name) : GattInterface(owner, name), service(service), pOnUpdatedValueFunc(nullptr) { } // Returning the owner pops us one level up the hierarchy // // This method compliments `GattService::gattCharacteristicBegin()` GattService &GattCharacteristic::gattCharacteristicEnd() { return service; } // Locates a D-Bus method within this D-Bus interface and invokes the method bool GattCharacteristic::callMethod(const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const { for (const DBusMethod &method : methods) { if (methodName == method.getName()) { method.call(pConnection, getPath(), getName(), methodName, pParameters, pInvocation, pUserData); return true; } } return false; } // Adds an event to the characteristic and returns a refereence to 'this` to enable method chaining in the server description // // NOTE: We specifically overload this method in order to accept our custom EventCallback type and transform it into a // TickEvent::Callback type. We also return our own type. This simplifies the server description by allowing call to chain. GattCharacteristic &GattCharacteristic::onEvent(int tickFrequency, void *pUserData, EventCallback callback) { events.push_back(TickEvent(this, tickFrequency, reinterpret_cast(callback), pUserData)); return *this; } // Ticks events within this characteristic // // Note: we specifically override this method in order to translate the generic TickEvent::Callback into our own EventCallback void GattCharacteristic::tickEvents(GDBusConnection *pConnection, void *pUserData) const { for (const TickEvent &event : events) { event.tick(getPath(), pConnection, pUserData); } } // Specialized support for ReadlValue method // // Defined as: array{byte} ReadValue(dict options) // // D-Bus breakdown: // // Input args: options - "a{sv}" // Output args: value - "ay" GattCharacteristic &GattCharacteristic::onReadValue(MethodCallback callback) { // array{byte} ReadValue(dict options) static const char *inArgs[] = {"a{sv}", nullptr}; addMethod("ReadValue", inArgs, "ay", reinterpret_cast(callback)); return *this; } // Specialized support for WriteValue method // // Defined as: void WriteValue(array{byte} value, dict options) // // D-Bus breakdown: // // Input args: value - "ay" // options - "a{sv}" // Output args: void GattCharacteristic &GattCharacteristic::onWriteValue(MethodCallback callback) { static const char *inArgs[] = {"ay", "a{sv}", nullptr}; addMethod("WriteValue", inArgs, nullptr, reinterpret_cast(callback)); return *this; } // Custom support for handling updates to our characteristic's value // // Defined as: (NOT defined by Bluetooth or BlueZ - this method is internal only) // // This method is called by our framework whenever a characteristic's value is updated. If you need to perform any actions // when a value is updatd, this is a good place to do that work. // // If you need to perform the same action(s) when a value is updated from the client (via `onWriteValue`) or from this server, // then it may be beneficial to call this method from within your onWriteValue callback to reduce duplicated code. See // `callOnUpdatedValue` for more information. GattCharacteristic &GattCharacteristic::onUpdatedValue(UpdatedValueCallback callback) { pOnUpdatedValueFunc = callback; return *this; } // Calls the onUpdatedValue method, if one was set. // // Returns false if there was no method set, otherwise, returns the boolean result of the method call. // // If you need to perform the same action(s) when a value is updated from the client (via onWriteValue) or from this server, // then it may be beneficial to place those actions in the `onUpdatedValue` method and call it from from within your // `onWriteValue` callback to reduce duplicated code. To call the `onUpdatedValue` method from within your `onWriteValue`, you // can use this pattern: // // .onWriteValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA // { // // Update your value // ... // // // Call the onUpdateValue method that was set in the same Characteristic // self.callOnUpdatedValue(pConnection, pUserData); // }) bool GattCharacteristic::callOnUpdatedValue(GDBusConnection *pConnection, void *pUserData) const { if (nullptr == pOnUpdatedValueFunc) { return false; } Logger::debug(SSTR << "Calling OnUpdatedValue function for interface at path '" << getPath() << "'"); return pOnUpdatedValueFunc(*this, pConnection, pUserData); } // Convenience functions to add a GATT descriptor to the hierarchy // // We simply add a new child at the given path and add an interface configured as a GATT descriptor to it. The // new descriptor is declared with a UUID and a variable argument list of flags (in string form.) For a complete and // up-to-date list of flag values, see: https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt // // At the time of this writing, the list of flags is as follows: // // "read" // "write" // "encrypt-read" // "encrypt-write" // "encrypt-authenticated-read" // "encrypt-authenticated-write" // "secure-read" (Server Only) // "secure-write" (Server Only) // // // To end a descriptor, call `GattDescriptor::gattDescriptorEnd()` GattDescriptor &GattCharacteristic::gattDescriptorBegin(const std::string &pathElement, const GattUuid &uuid, const std::vector &flags) { DBusObject &child = owner.addChild(DBusObjectPath(pathElement)); GattDescriptor &descriptor = *child.addInterface(std::make_shared(child, *this, "org.bluez.GattDescriptor1")); descriptor.addProperty("UUID", uuid); descriptor.addProperty("Characteristic", getPath()); descriptor.addProperty("Flags", flags); return descriptor; } // Sends a change notification to subscribers to this characteristic // // This is a generalized method that accepts a `GVariant *`. A templated version is available that supports common types called // `sendChangeNotificationValue()`. void GattCharacteristic::sendChangeNotificationVariant(GDBusConnection *pBusConnection, GVariant *pNewValue) const { g_auto(GVariantBuilder) builder; g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); g_variant_builder_add(&builder, "{sv}", "Value", pNewValue); GVariant *pSasv = g_variant_new("(sa{sv})", "org.bluez.GattCharacteristic1", &builder); owner.emitSignal(pBusConnection, "org.freedesktop.DBus.Properties", "PropertiesChanged", pSasv); } }; // namespace ggk