simplersble 1.1.1-dev29

The all-in-one Bluetooth library that makes it easy to add wireless connectivity to your projects.
Documentation
#include "LocalCharacteristicAndroid.h"

#include <optional>
#include <utility>

#include <simpleble/Exceptions.h>

#include "CommonUtils.h"
#include "LocalPeripheralAndroid.h"

namespace SimpleBLE::Local {

CharacteristicAndroid::CharacteristicAndroid(std::weak_ptr<PeripheralAndroid> peripheral, BluetoothUUID uuid,
                                             std::set<CharacteristicCapability> capabilities)
    : _peripheral(std::move(peripheral)), _uuid(std::move(uuid)), _capabilities(std::move(capabilities)) {
    if (_capabilities.empty()) {
        throw Exception::OperationFailed("A local characteristic requires at least one capability.");
    }

    using Native = Android::BluetoothGattCharacteristic;
    int properties = 0;
    int permissions = 0;
    for (const auto capability : _capabilities) {
        switch (capability) {
            case CharacteristicCapability::READ:
                properties |= Native::PROPERTY_READ;
                permissions |= Native::PERMISSION_READ;
                break;
            case CharacteristicCapability::WRITE_REQUEST:
                properties |= Native::PROPERTY_WRITE;
                permissions |= Native::PERMISSION_WRITE;
                break;
            case CharacteristicCapability::WRITE_COMMAND:
                properties |= Native::PROPERTY_WRITE_NO_RESPONSE;
                permissions |= Native::PERMISSION_WRITE;
                break;
            case CharacteristicCapability::NOTIFY:
                properties |= Native::PROPERTY_NOTIFY;
                break;
            case CharacteristicCapability::INDICATE:
                properties |= Native::PROPERTY_INDICATE;
                break;
        }
    }
    _characteristic = Native(_uuid, properties, permissions);

    if (_capabilities.count(CharacteristicCapability::NOTIFY) > 0 ||
        _capabilities.count(CharacteristicCapability::INDICATE) > 0) {
        _cccd.emplace(
            Android::BluetoothGattDescriptor::CLIENT_CHARACTERISTIC_CONFIG,
            Android::BluetoothGattDescriptor::PERMISSION_READ | Android::BluetoothGattDescriptor::PERMISSION_WRITE);
        if (!_characteristic.addDescriptor(*_cccd)) {
            throw Exception::OperationFailed("Android failed to add the client configuration descriptor.");
        }
    }
}

CharacteristicAndroid::~CharacteristicAndroid() {
    _callback_on_read.unload();
    _callback_on_write.unload();
    _callback_on_subscribed.unload();
    _callback_on_unsubscribed.unload();
}

BluetoothUUID CharacteristicAndroid::uuid() { return _uuid; }
std::set<CharacteristicCapability> CharacteristicAndroid::capabilities() { return _capabilities; }

ByteArray CharacteristicAndroid::value() {
    std::scoped_lock lock(_mutex);
    return _value;
}

void CharacteristicAndroid::set_value(ByteArray value) {
    ByteArray published;
    {
        std::scoped_lock lock(_mutex);
        _value = std::move(value);
        published = _value;
    }
    if (auto peripheral = _peripheral.lock()) peripheral->publish(shared_from_this(), published);
}

void CharacteristicAndroid::set_callback_on_read(std::function<ByteArray()> callback) {
    if (callback)
        _callback_on_read.load(std::move(callback));
    else
        _callback_on_read.unload();
}
void CharacteristicAndroid::set_callback_on_write(std::function<void(ByteArray)> callback) {
    if (callback)
        _callback_on_write.load(std::move(callback));
    else
        _callback_on_write.unload();
}
void CharacteristicAndroid::set_callback_on_subscribed(std::function<void()> callback) {
    if (callback)
        _callback_on_subscribed.load(std::move(callback));
    else
        _callback_on_subscribed.unload();
}
void CharacteristicAndroid::set_callback_on_unsubscribed(std::function<void()> callback) {
    if (callback)
        _callback_on_unsubscribed.load(std::move(callback));
    else
        _callback_on_unsubscribed.unload();
}

Android::BluetoothGattCharacteristic CharacteristicAndroid::native_characteristic() const { return _characteristic; }
std::optional<Android::BluetoothGattDescriptor> CharacteristicAndroid::native_cccd() const { return _cccd; }

bool CharacteristicAndroid::can_read() const { return _capabilities.count(CharacteristicCapability::READ) > 0; }
bool CharacteristicAndroid::can_write_request() const {
    return _capabilities.count(CharacteristicCapability::WRITE_REQUEST) > 0;
}
bool CharacteristicAndroid::can_write_command() const {
    return _capabilities.count(CharacteristicCapability::WRITE_COMMAND) > 0;
}

ByteArray CharacteristicAndroid::handle_read() {
    if (_callback_on_read) {
        ByteArray dynamic = _callback_on_read();
        std::scoped_lock lock(_mutex);
        _value = dynamic;
        return dynamic;
    }
    return value();
}

void CharacteristicAndroid::handle_write(ByteArray value) {
    {
        std::scoped_lock lock(_mutex);
        _value = value;
    }
    SAFE_CALLBACK_CALL(_callback_on_write, std::move(value));
}

bool CharacteristicAndroid::set_subscription(const Android::BluetoothDevice& device, const ByteArray& value) {
    const ByteArray disabled(Android::BluetoothGattDescriptor::DISABLE_NOTIFICATION_VALUE);
    const ByteArray notifications(Android::BluetoothGattDescriptor::ENABLE_NOTIFICATION_VALUE);
    const ByteArray indications(Android::BluetoothGattDescriptor::ENABLE_INDICATION_VALUE);
    const BluetoothAddress address(device.getAddress());
    bool subscribed = false;
    bool notify_subscribed = false;
    bool notify_unsubscribed = false;
    {
        std::scoped_lock lock(_mutex);
        const bool was_empty = _subscribers.empty();
        if (value == disabled) {
            if (_subscribers.erase(address) > 0 && _subscribers.empty()) notify_unsubscribed = true;
        } else if (value == notifications && _capabilities.count(CharacteristicCapability::NOTIFY) > 0) {
            _subscribers.insert_or_assign(address, Subscriber{device, false});
            subscribed = true;
            notify_subscribed = was_empty;
        } else if (value == indications && _capabilities.count(CharacteristicCapability::INDICATE) > 0) {
            _subscribers.insert_or_assign(address, Subscriber{device, true});
            subscribed = true;
            notify_subscribed = was_empty;
        } else {
            return false;
        }
    }
    if (notify_subscribed) SAFE_CALLBACK_CALL(_callback_on_subscribed);
    if (notify_unsubscribed) SAFE_CALLBACK_CALL(_callback_on_unsubscribed);
    return subscribed || value == disabled;
}

ByteArray CharacteristicAndroid::subscription_value(const BluetoothAddress& address) const {
    std::scoped_lock lock(_mutex);
    auto subscriber = _subscribers.find(address);
    if (subscriber == _subscribers.end()) {
        return ByteArray(Android::BluetoothGattDescriptor::DISABLE_NOTIFICATION_VALUE);
    }
    return subscriber->second.confirm ? ByteArray(Android::BluetoothGattDescriptor::ENABLE_INDICATION_VALUE)
                                      : ByteArray(Android::BluetoothGattDescriptor::ENABLE_NOTIFICATION_VALUE);
}

std::vector<CharacteristicAndroid::Subscriber> CharacteristicAndroid::subscribers() const {
    std::scoped_lock lock(_mutex);
    std::vector<Subscriber> result;
    result.reserve(_subscribers.size());
    for (const auto& [address, subscriber] : _subscribers) result.push_back(subscriber);
    return result;
}

void CharacteristicAndroid::remove_subscriber(const BluetoothAddress& address) {
    bool notify = false;
    {
        std::scoped_lock lock(_mutex);
        if (_subscribers.erase(address) > 0 && _subscribers.empty()) notify = true;
    }
    if (notify) SAFE_CALLBACK_CALL(_callback_on_unsubscribed);
}

void CharacteristicAndroid::clear_subscribers() {
    std::scoped_lock lock(_mutex);
    _subscribers.clear();
}

}  // namespace SimpleBLE::Local