/* * Author: Sven Gothel * Copyright (c) 2020 Gothel Software e.K. * Copyright (c) 2020 ZAFENA AB * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef GATT_CHARACTERISTIC_HPP_ #define GATT_CHARACTERISTIC_HPP_ #include #include #include #include #include #include #include #include #include "UUID.hpp" #include "BTTypes.hpp" #include "OctetTypes.hpp" #include "ATTPDUTypes.hpp" #include "DBTTypes.hpp" #include "GATTDescriptor.hpp" /** * - - - - - - - - - - - - - - - * * Module GATTCharacteristic: * * - BT Core Spec v5.2: Vol 3, Part G Generic Attribute Protocol (GATT) * - BT Core Spec v5.2: Vol 3, Part G GATT: 2.6 GATT Profile Hierarchy */ namespace direct_bt { class GATTCharacteristicListener; // forward class GATTHandler; // forward class GATTService; // forward typedef std::shared_ptr GATTServiceRef; /** *

* BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1 Characteristic Declaration Attribute Value *

* handle -> CDAV value *

* BT Core Spec v5.2: Vol 3, Part G GATT: 4.6.1 Discover All Characteristics of a Service * * Here the handle is a service's characteristics-declaration * and the value the Characteristics Property, Characteristics Value Handle _and_ Characteristics UUID. *

*/ class GATTCharacteristic : public DBTObject { private: /** Characteristics's service weak back-reference */ std::weak_ptr wbr_service; bool enabledNotifyState = false; bool enabledIndicateState = false; std::string toShortString() const noexcept; public: /** BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.1.1 Characteristic Properties */ enum PropertyBitVal : uint8_t { Broadcast = 0x01, Read = 0x02, WriteNoAck = 0x04, WriteWithAck = 0x08, Notify = 0x10, Indicate = 0x20, AuthSignedWrite = 0x40, ExtProps = 0x80 }; /** * Returns string values as defined in *
             * org.bluez.GattCharacteristic1 :: array{string} Flags [read-only]
             * 
*/ static std::string getPropertyString(const PropertyBitVal prop) noexcept; static std::string getPropertiesString(const PropertyBitVal properties) noexcept; static std::vector> getPropertiesStringList(const PropertyBitVal properties) noexcept; /** * Characteristics's Service Handle - key to service's handle range, retrieved from Characteristics data. *

* Attribute handles are unique for each device (server) (BT Core Spec v5.2: Vol 3, Part F Protocol..: 3.2.2 Attribute Handle). *

*/ const uint16_t service_handle; /** * Characteristic Handle of this instance. *

* Attribute handles are unique for each device (server) (BT Core Spec v5.2: Vol 3, Part F Protocol..: 3.2.2 Attribute Handle). *

*/ const uint16_t handle; /* Characteristics Property */ const PropertyBitVal properties; /** * Characteristics Value Handle. *

* Attribute handles are unique for each device (server) (BT Core Spec v5.2: Vol 3, Part F Protocol..: 3.2.2 Attribute Handle). *

*/ const uint16_t value_handle; /* Characteristics Value Type UUID */ std::shared_ptr value_type; /** List of Characteristic Descriptions as shared reference */ std::vector descriptorList; /* Optional Client Characteristic Configuration index within descriptorList */ int clientCharacteristicsConfigIndex = -1; GATTCharacteristic(const GATTServiceRef & service_, const uint16_t service_handle_, const uint16_t handle_, const PropertyBitVal properties_, const uint16_t value_handle_, std::shared_ptr value_type_) noexcept : wbr_service(service_), service_handle(service_handle_), handle(handle_), properties(properties_), value_handle(value_handle_), value_type(value_type_) {} std::string get_java_class() const noexcept override { return java_class(); } static std::string java_class() noexcept { return std::string(JAVA_DBT_PACKAGE "DBTGattCharacteristic"); } std::shared_ptr getServiceUnchecked() const noexcept { return wbr_service.lock(); } std::shared_ptr getServiceChecked() const; std::shared_ptr getGATTHandlerUnchecked() const noexcept; std::shared_ptr getGATTHandlerChecked() const; std::shared_ptr getDeviceUnchecked() const noexcept; std::shared_ptr getDeviceChecked() const; bool hasProperties(const PropertyBitVal v) const noexcept { return v == ( properties & v ); } std::string getPropertiesString() const noexcept { return getPropertiesString(properties); } std::string toString() const noexcept override; void clearDescriptors() noexcept { descriptorList.clear(); clientCharacteristicsConfigIndex = -1; } GATTDescriptorRef getClientCharacteristicConfig() noexcept { if( 0 > clientCharacteristicsConfigIndex ) { return nullptr; } return descriptorList.at(static_cast(clientCharacteristicsConfigIndex)); // abort if out of bounds } /** * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration *

* Method enables notification and/or indication for this characteristic at BLE level. *

*

* Implementation masks this Characteristic properties PropertyBitVal::Notify and PropertyBitVal::Indicate * with the respective user request parameters, hence removes unsupported requests. *

*

* Notification and/or indication configuration is only performed per characteristic if changed. *

*

* It is recommended to utilize notification over indication, as its link-layer handshake * and higher potential bandwidth may deliver material higher performance. *

* @param enableNotification * @param enableIndication * @param enabledState array of size 2, holding the resulting enabled state for notification and indication. * @return false if this characteristic has no PropertyBitVal::Notify or PropertyBitVal::Indication present, * or there is no GATTDescriptor of type ClientCharacteristicConfiguration, or if the operation has failed. * Otherwise returns true. * @throws IllegalStateException if notification or indication is set to be enabled * and the {@link DBTDevice's}'s {@link GATTHandler} is null, i.e. not connected */ bool configNotificationIndication(const bool enableNotification, const bool enableIndication, bool enabledState[2]); /** * BT Core Spec v5.2: Vol 3, Part G GATT: 3.3.3.3 Client Characteristic Configuration *

* Method will attempt to enable notification on the BLE level, if available, * otherwise indication if available. *

*

* Notification and/or indication configuration is only performed per characteristic if changed. *

*

* It is recommended to utilize notification over indication, as its link-layer handshake * and higher potential bandwidth may deliver material higher performance. *

* @param enabledState array of size 2, holding the resulting enabled state for notification and indication. * @return false if this characteristic has no PropertyBitVal::Notify or PropertyBitVal::Indication present, * or there is no GATTDescriptor of type ClientCharacteristicConfiguration, or if the operation has failed. * Otherwise returns true. * @throws IllegalStateException if notification or indication is set to be enabled * and the {@link DBTDevice's}'s {@link GATTHandler} is null, i.e. not connected */ bool enableNotificationOrIndication(bool enabledState[2]); /** * Add the given GATTCharacteristicListener to the listener list if not already present. *

* Occurring notifications and indications, if enabled via configNotificationIndication(bool, bool, bool[]) * or enableNotificationOrIndication(bool[]), * will call the respective GATTCharacteristicListener callback method. *

*

* Returns true if the given listener is not element of the list and has been newly added, * otherwise false. *

*

* Convenience delegation call to GATTHandler via DBTDevice *

*

* To restrict the listener to listen only to this GATTCharacteristic instance, * user has to implement GATTCharacteristicListener::match(GATTCharacteristicRef) accordingly. *
* For this purpose, use may derive from AssociatedGATTCharacteristicListener, * which provides these simple matching filter facilities. *

* @throws IllegalStateException if the {@link DBTDevice's}'s {@link GATTHandler} is null, i.e. not connected */ bool addCharacteristicListener(std::shared_ptr l); /** * Add the given GATTCharacteristicListener to the listener list if not already present * and if enabling the notification or indication for this characteristic at BLE level was successful.
* Notification and/or indication configuration is only performed per characteristic if changed. *

* Implementation will attempt to enable notification only, if available, * otherwise indication if available.
* Implementation uses enableNotificationOrIndication(bool[]) to enable either. *

*

* Occurring notifications and indications will call the respective GATTCharacteristicListener * callback method. *

*

* Returns true if enabling the notification and/or indication was successful * and if the given listener is not element of the list and has been newly added, * otherwise false. *

*

* To restrict the listener to listen only to this GATTCharacteristic instance, * user has to implement GATTCharacteristicListener::match(GATTCharacteristicRef) accordingly. *
* For this purpose, use may derive from AssociatedGATTCharacteristicListener, * which provides these simple matching filter facilities. *

* @param enabledState array of size 2, holding the resulting enabled state for notification and indication * using enableNotificationOrIndication(bool[]) * @throws IllegalStateException if the {@link DBTDevice's}'s {@link GATTHandler} is null, i.e. not connected */ bool addCharacteristicListener(std::shared_ptr l, bool enabledState[2]); /** * Disables the notification and/or indication for this characteristic at BLE level * if {@code disableIndicationNotification == true} * and removes the given {@link GATTCharacteristicListener} from the listener list. *

* Returns true if the given listener is an element of the list and has been removed, * otherwise false. *

*

* Convenience delegation call to GATTHandler via DBTDevice * performing addCharacteristicListener(..) * and {@link #configNotificationIndication(bool, bool, bool[]) if {@code disableIndicationNotification == true}. *

*

* If the DBTDevice's GATTHandler is null, i.e. not connected, {@code false} is being returned. *

* @param l * @param disableIndicationNotification if true, disables the notification and/or indication for this characteristic * using {@link #configNotificationIndication(bool, bool, bool[]) * @return */ bool removeCharacteristicListener(std::shared_ptr l, bool disableIndicationNotification); /** * Disables the notification and/or indication for this characteristic at BLE level * if {@code disableIndicationNotification == true} * and removes all {@link GATTCharacteristicListener} from the listener list. *

* Returns the number of removed event listener. *

*

* Convenience delegation call to GATTHandler via DBTDevice * performing addCharacteristicListener(..) * and configNotificationIndication(..) if {@code disableIndicationNotification == true}. *

*

* If the DBTDevice's GATTHandler is null, i.e. not connected, {@code zero} is being returned. *

* @param disableIndicationNotification if true, disables the notification and/or indication for this characteristic * using {@link #configNotificationIndication(bool, bool, bool[]) * @return */ int removeAllCharacteristicListener(bool disableIndicationNotification); /** * BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.1 Read Characteristic Value *

* BT Core Spec v5.2: Vol 3, Part G GATT: 4.8.3 Read Long Characteristic Value *

*

* If expectedLength = 0, then only one ATT_READ_REQ/RSP will be used. *

*

* If expectedLength < 0, then long values using multiple ATT_READ_BLOB_REQ/RSP will be used until * the response returns zero. This is the default parameter. *

*

* If expectedLength > 0, then long values using multiple ATT_READ_BLOB_REQ/RSP will be used * if required until the response returns zero. *

*

* Convenience delegation call to GATTHandler via DBTDevice *

*

* If the DBTDevice's GATTHandler is null, i.e. not connected, an IllegalStateException is thrown. *

*/ bool readValue(POctets & res, int expectedLength=-1); /** * BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.3 Write Characteristic Value *

* Convenience delegation call to GATTHandler via DBTDevice *

*

* If the DBTDevice's GATTHandler is null, i.e. not connected, an IllegalStateException is thrown. *

*/ bool writeValue(const TROOctets & value); /** * BT Core Spec v5.2: Vol 3, Part G GATT: 4.9.1 Write Characteristic Value Without Response *

* Convenience delegation call to GATTHandler via DBTDevice *

*

* If the DBTDevice's GATTHandler is null, i.e. not connected, an IllegalStateException is thrown. *

*/ bool writeValueNoResp(const TROOctets & value); }; typedef std::shared_ptr GATTCharacteristicRef; inline bool operator==(const GATTCharacteristic& lhs, const GATTCharacteristic& rhs) noexcept { return lhs.handle == rhs.handle; /** unique attribute handles */ } inline bool operator!=(const GATTCharacteristic& lhs, const GATTCharacteristic& rhs) noexcept { return !(lhs == rhs); } /** * {@link GATTCharacteristic} event listener for notification and indication events. *

* A listener instance may be attached to a {@link BluetoothGattCharacteristic} via * {@link GATTCharacteristic::addCharacteristicListener(std::shared_ptr)} to listen to events, * see method's API doc for {@link GATTCharacteristic} filtering. *

*

* User may utilize {@link AssociatedGATTCharacteristicListener} to listen to only one {@link GATTCharacteristic}. *

*

* A listener instance may be attached to a {@link GATTHandler} via * {@link GATTHandler::addCharacteristicListener(std::shared_ptr)} * to listen to all events of the device or the matching filtered events. *

*

* The listener receiver maintains a unique set of listener instances without duplicates. *

*/ class GATTCharacteristicListener { public: /** * Custom filter for all event methods, * which will not be called if this method returns false. *

* User may override this method to test whether the methods shall be called * for the given GATTCharacteristic. *

*

* Defaults to true; *

*/ virtual bool match(const GATTCharacteristic & characteristic) noexcept { (void)characteristic; return true; } /** * Called from native BLE stack, initiated by a received notification associated * with the given {@link GATTCharacteristic}. * @param charDecl {@link GATTCharacteristic} related to this notification * @param charValue the notification value * @param timestamp the indication monotonic timestamp, see getCurrentMilliseconds() */ virtual void notificationReceived(GATTCharacteristicRef charDecl, std::shared_ptr charValue, const uint64_t timestamp) = 0; /** * Called from native BLE stack, initiated by a received indication associated * with the given {@link GATTCharacteristic}. * @param charDecl {@link GATTCharacteristic} related to this indication * @param charValue the indication value * @param timestamp the indication monotonic timestamp, see {@link BluetoothUtils#getCurrentMilliseconds()} * @param confirmationSent if true, the native stack has sent the confirmation, otherwise user is required to do so. */ virtual void indicationReceived(GATTCharacteristicRef charDecl, std::shared_ptr charValue, const uint64_t timestamp, const bool confirmationSent) = 0; virtual ~GATTCharacteristicListener() noexcept {} /** * Default comparison operator, merely testing for same memory reference. *

* Specializations may override. *

*/ virtual bool operator==(const GATTCharacteristicListener& rhs) const noexcept { return this == &rhs; } bool operator!=(const GATTCharacteristicListener& rhs) const noexcept { return !(*this == rhs); } }; class AssociatedGATTCharacteristicListener : public GATTCharacteristicListener{ private: const GATTCharacteristic * associatedCharacteristic; public: /** * Passing the associated GATTCharacteristic to filter out non matching events. */ AssociatedGATTCharacteristicListener(const GATTCharacteristic * characteristicMatch) noexcept : associatedCharacteristic(characteristicMatch) { } bool match(const GATTCharacteristic & characteristic) noexcept override { if( nullptr == associatedCharacteristic ) { return true; } return *associatedCharacteristic == characteristic; } }; } // namespace direct_bt #endif /* GATT_CHARACTERISTIC_HPP_ */