Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Example code for a GattServer #63

Open
mtexplorative opened this issue Aug 15, 2024 · 2 comments
Open

Example code for a GattServer #63

mtexplorative opened this issue Aug 15, 2024 · 2 comments

Comments

@mtexplorative
Copy link

mtexplorative commented Aug 15, 2024

Hi,
we are trying to implement Bluetooth as an additional path to control an IoT-Device.
Our current set-up regarding the compute hardware is a Raspberry Pi (may changed later) but will still be Linux ;)
Our functionality is already accessible via REST-calls provided by a micronaut-based Java program.
I struggle in setting-up a GattServer with this library.
I would appreciate it if a simple example code could be provided showing how to provide a read function on a property (incl. advertsing it).
Regards, Markus.

@hypfvieh
Copy link
Owner

Providing a Gatt-Server is not really straight forward using bluez and dbus.
Without any warranty, here is some sample code, maybe you can start with that. Other samples in Python and C can be found in the bluez.git Repository (see tools and test directory).

package com.github.hypfvieh.sandbox.bluez;

import org.bluez.GattCharacteristic1;
import org.bluez.GattDescriptor1;
import org.bluez.GattManager1;
import org.bluez.GattService1;
import org.bluez.datatypes.TwoTuple;
import org.bluez.exceptions.*;
import org.freedesktop.dbus.DBusPath;
import org.freedesktop.dbus.FileDescriptor;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder;
import org.freedesktop.dbus.interfaces.DBusInterface;
import org.freedesktop.dbus.interfaces.Properties;
import org.freedesktop.dbus.types.UInt16;
import org.freedesktop.dbus.types.Variant;

import java.util.*;

public class GattExample {
    static final String FLAG_READ = "read";
    static final String FLAG_WRITE = "write";

    public static void main(String[] args) {

        MyGattApplication myGattApplication = new MyGattApplication();
        SampleService sampleService = new SampleService();
        SampleCharacteristics sampleCharacteristics = new SampleCharacteristics(sampleService);
        SampleGattDescriptor sampleDescriptor = new SampleGattDescriptor(sampleCharacteristics);
        sampleCharacteristics.getDescriptors().add(sampleDescriptor);
        myGattApplication.addService(sampleService);

        sampleService.getCharacteristics().add(sampleCharacteristics);

        try (DBusConnection dbus = DBusConnectionBuilder.forSystemBus().build()) {

            dbus.exportObject(myGattApplication);

            GattManager1 gattMgr = dbus.getRemoteObject("org.bluez", "/org/bluez/hci0", GattManager1.class);

            Map<String, Variant<?>> opts = new HashMap<>();

            gattMgr.RegisterApplication(new DBusPath(myGattApplication.getObjectPath()), opts);
            Thread.sleep(50000000L); // do something useful or a proper wait loop
        } catch (Exception _ex) {
            // TODO Auto-generated catch block
            _ex.printStackTrace();
        }
    }

    public static class MyGattApplication implements GattServiceApp {

        private final List<SampleService> services = new ArrayList<>();

        @Override
        public String getObjectPath() {
            return "/";
        }

        public void addService(SampleService _service) {
            services.add(_service);
        }

        @Override
        public Map<DBusPath, Map<String, Map<String, Variant<?>>>> GetManagedObjects() {

            Map<DBusPath, Map<String, Map<String, Variant<?>>>> result = new LinkedHashMap<>();

            for (SampleService svc : services) {
                result.put(new DBusPath(svc.getObjectPath()), svc.getProps());
                for (SampleCharacteristics chr : svc.getCharacteristics()) {
                    result.put(new DBusPath(chr.getObjectPath()), chr.getProps());
                    for (SampleGattDescriptor descriptor : chr.getDescriptors()) {
                        result.put(new DBusPath(descriptor.getObjectPath()), descriptor.getProps());
                    }
                }
            }

            return result;
        }

    }

    public static class SampleService implements GattService1, PropertiesHelper {
        static final String BASE_PATH = "/mygatt/application/";
        // This should be a static value, not a random one
        private final String uuid = UUID.randomUUID().toString();
        private final Boolean primary = true;
        private final List<SampleCharacteristics> characteristics = new ArrayList<>();

        public List<SampleCharacteristics> getCharacteristics() {
            return characteristics;
        }

        @Override
        public String getObjectPath() {
            return BASE_PATH + "service0";
        }

        public String getUuid() {
            return uuid;
        }

        public boolean isPrimary() {
            return primary;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Variant<?> Get(String _interfaceName, String _propertyName) {
            if (!getBluezInterface().getName().equals(_interfaceName)) {
                throw new IllegalArgumentException();
            }

            if ("UUID".equals(_propertyName)) {
                return new Variant<>(uuid);
            } else if ("Primary".equals(_propertyName)) {
                return new Variant<>(primary);
            } else if ("Characteristics".equals(_propertyName)) {
                return getCharacteristicsAsVariantArray();
            }
            return null;
        }

        @Override
        public <A> void Set(String _interfaceName, String _propertyName, A _value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Map<String, Variant<?>> GetAll(String _interfaceName) {
            return Map.of("UUID", new Variant<>(uuid),
                "Primary", new Variant<>(primary),
                "Characteristics", getCharacteristicsAsVariantArray()
                );
        }

        @Override
        public Class<? extends DBusInterface> getBluezInterface() {
            return GattService1.class;
        }

        private Variant<List<DBusPath>> getCharacteristicsAsVariantArray() {
            List<DBusPath> pathes = new ArrayList<>();
            getCharacteristics().stream().map(d -> new DBusPath(d.getObjectPath())).forEach(pathes::add);

            return new Variant<>(pathes, "ao");
        }

    }

    public static class SampleCharacteristics implements GattCharacteristic1, PropertiesHelper {

        private final String uuid = UUID.randomUUID().toString();

        private final SampleService service;
        private final List<String> flags = new ArrayList<>();
        private final List<SampleGattDescriptor> descriptors = new ArrayList<>();

        public SampleCharacteristics(SampleService _service) {
            service = _service;
            flags.add(FLAG_READ);
        }

        public String getUuid() {
            return uuid;
        }

        public SampleService getService() {
            return service;
        }

        public List<SampleGattDescriptor> getDescriptors() {
            return descriptors;
        }

        @Override
        public Class<? extends DBusInterface> getBluezInterface() {
            return GattCharacteristic1.class;
        }

        @Override
        public String getObjectPath() {
            return service.getObjectPath() + "/char0";
        }

        @Override
        public byte[] ReadValue(Map<String, Variant<?>> _options)
            throws BluezFailedException, BluezInProgressException, BluezNotPermittedException, BluezNotAuthorizedException, BluezInvalidOffsetException, BluezNotSupportedException {

            throw new UnsupportedOperationException("Implement if needed");
        }

        @Override
        public void WriteValue(byte[] _value, Map<String, Variant<?>> _options)
            throws BluezFailedException, BluezInProgressException, BluezNotPermittedException, BluezInvalidValueLengthException, BluezNotAuthorizedException, BluezNotSupportedException {
            throw new UnsupportedOperationException("Implement if needed");

        }

        @Override
        public TwoTuple<FileDescriptor, UInt16> AcquireWrite(Map<String, Variant<?>> _options) throws BluezFailedException, BluezNotSupportedException {
            throw new UnsupportedOperationException("Implement if needed");
        }

        @Override
        public TwoTuple<FileDescriptor, UInt16> AcquireNotify(Map<String, Variant<?>> _options) throws BluezFailedException, BluezNotSupportedException {
            throw new UnsupportedOperationException("Implement if needed");
        }

        @Override
        public void StartNotify() throws BluezFailedException, BluezNotPermittedException, BluezInProgressException, BluezNotConnectedException, BluezNotSupportedException {
            throw new UnsupportedOperationException("Implement if needed");

        }

        @Override
        public void StopNotify() throws BluezFailedException {
            throw new UnsupportedOperationException("Implement if needed");

        }

        @Override
        public void Confirm() throws BluezFailedException {
            throw new UnsupportedOperationException("Implement if needed");
        }

        @SuppressWarnings("unchecked")
        @Override
        public Variant<?> Get(String _interfaceName, String _propertyName) {
            if (!getBluezInterface().getName().equals(_interfaceName)) {
                throw new IllegalArgumentException();
            }

            if ("UUID".equals(_propertyName)) {
                return  new Variant<>(uuid);
            } else if ("Service".equals(_propertyName)) {
                return new Variant<>(new DBusPath(service.getObjectPath()));
            } else if ("Descriptors".equals(_propertyName)) {
                return getDescriptorsAsVariantArray();
            } else if ("Flags".equals(_propertyName)) {
                return  new Variant<>(flags, "as");
            }
            return null;
        }

        @Override
        public <A> void Set(String _interfaceName, String _propertyName, A _value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Map<String, Variant<?>> GetAll(String _interfaceName) {
            return Map.of("UUID", new Variant<>(uuid),
                "Service", new Variant<>(new DBusPath(service.getObjectPath())),
                "Descriptors", getDescriptorsAsVariantArray(),
                "Flags", new Variant<>(flags, "as"));
        }

        private Variant<List<DBusPath>> getDescriptorsAsVariantArray() {
            List<DBusPath> pathes = new ArrayList<>();
            getDescriptors().stream().map(d -> new DBusPath(d.getObjectPath())).forEach(pathes::add);

            return new Variant<>(pathes, "ao");
        }

    }

    public static class SampleGattDescriptor implements GattDescriptor1, PropertiesHelper {

        private final String uuid = UUID.randomUUID().toString();
        private final SampleCharacteristics characteristic;
        private final List<String> flags = new ArrayList<>();

        public SampleGattDescriptor(SampleCharacteristics _characteristics) {
            characteristic = _characteristics;
            flags.add(FLAG_READ);
        }

        public String getUuid() {
            return uuid;
        }

        public SampleCharacteristics getCharacteristic() {
            return characteristic;
        }

        @Override
        public Class<? extends DBusInterface> getBluezInterface() {
            return GattDescriptor1.class;
        }

        @Override
        public String getObjectPath() {
            return characteristic.getObjectPath() + "/desc0";
        }

        @Override
        public byte[] ReadValue(Map<String, Variant<?>> _flags)
            throws BluezFailedException, BluezInProgressException, BluezNotPermittedException, BluezNotAuthorizedException, BluezNotSupportedException {
            throw new UnsupportedOperationException("Implement if needed");
        }

        @Override
        public void WriteValue(byte[] _value, Map<String, Variant<?>> _flags)
            throws BluezFailedException, BluezInProgressException, BluezNotPermittedException, BluezInvalidValueLengthException, BluezNotAuthorizedException, BluezNotSupportedException {
            throw new UnsupportedOperationException("Implement if needed");
        }

        @SuppressWarnings("unchecked")
        @Override
        public Variant<?> Get(String _interfaceName, String _propertyName) {
            if ("UUID".equals(_propertyName)) {
                return new Variant<>(uuid);
            } else if ("Characteristic  ".equals(_propertyName)) {
                return new Variant<>(characteristic);
            } else if ("Flags".equals(_propertyName)) {
                return new Variant<>(flags, "as");
            }
            return null;
        }

        @Override
        public <A> void Set(String _interfaceName, String _propertyName, A _value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Map<String, Variant<?>> GetAll(String _interfaceName) {
            return Map.of("UUID", new Variant<>(uuid),
                "Characteristic", new Variant<>(new DBusPath(characteristic.getObjectPath())),
                "Flags", new Variant<>(flags, "as"));
        }


    }

    public interface GattServiceApp extends DBusInterface {

        Map<DBusPath, Map<String, Map<String, Variant<?>>>> GetManagedObjects();
    }

    public interface PropertiesHelper extends Properties {

        Class<? extends DBusInterface> getBluezInterface();

        default Map<String, Map<String, Variant<?>>> getProps() {
            Map<String, Map<String, Variant<?>>> result = new LinkedHashMap<>();
            result.put(getBluezInterface().getName(), GetAll(null));
            return result;
        }


    }
}

@mtexplorative
Copy link
Author

mtexplorative commented Aug 16, 2024 via email

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants