Skip to content

API Enhancements #2

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
.project
.classpath

# ignore the intelliJ project file
# ignore JetBrains IntilliJ project files
/out
/classes
*.iml
Expand Down
36 changes: 23 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,61 @@

**ble-java** is a java library for building **BLE** GATT peripherals role application in JAVA.

**ble-java** is based on BlueZ, the linux Bluetooth stack.
**ble-java** is based on BlueZ, the Linux Bluetooth stack implemented on D-Bus.

# Features
* Create GATT Services
* Create GATT Characteristic
* Customize the Peripheral name
* Pure JAVA library
* JAVA library with minimal JNI interfaces to BlueZ over D-Bus

# Dependencies
1. Java 8 or better
2. BlueZ 5.43 or better
3. d-bus Java library `libdbus-java`
Raspbian example:
3. libunixsocket-java (```apt-get install libsocket-java```)
4. d-bus Java library `libdbus-java`

Raspbian install:
```
sudo apt-get install
sudo apt-get install libdbus-java
```
you may also have to do run
```
sudo ldconfig
```

# Install
Clone the repository and build with Gradle (4.5 or higher):
Clone the repository and build with Gradle:
```
gradle build
./gradlew build
```

# Example
You could see the main `MainExample.java` in `src/test/java/example`.
It's a sample main that create a BLE Application with one Service and 2 Characteristic.
`MainExample.java` in `src/test/java/example` demonstrates a sample main that
creates a BLE Application with one Service and 2 Characteristics.

Before running the example, copy ```example.conf``` to ```/etc/dbus-1/system.d/```

To run from command line: ````sudo ./gradlew runExample````

Press ctrl-c to stop service.

# BlueZ compatibility
Until now is tested with BlueZ 5.46 on Raspbian distribution.
Tested with BlueZ 5.46 on Raspbian distribution.

ble-java use the GattManager and LEAdvertising that was marked "Experimental" since 5.47, so you have to enable the Experimental features with the `--experimental` parameter in the BlueZ startup service.
**ble-java** usees the GattManager and LEAdvertising that was marked "Experimental" since 5.47. You need to enable the
Experimental features with the `--experimental` parameter in the BlueZ startup service.

Example for Raspbian `/lib/systemd/system/bluetooth.service`
For Raspbian and Ubuntu `/lib/systemd/system/bluetooth.service`

```
...
bluetoohd --experimental
...
```

In the BlueZ 5.48 seem to be removed the experimental tag on the LEAdvertising features, but it was not yet tried.
If you are using BlueZ 5.48, the ```--experimental``` tag may not be needed for LEAdvertising features. But we have not tried
this yet.

For more info about BlueZ see [http://www.bluez.org](http://www.bluez.org).

Expand Down
42 changes: 19 additions & 23 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,24 @@
* user guide available at https://docs.gradle.org/4.5/userguide/java_library_plugin.html
*/

buildscript {
version = '0.1'
}
// Apply the java plugin to add support for Java
apply plugin: 'java-library'
apply plugin: 'maven'

project.version = '0.2'
project.group = 'it.tangodev'

plugins {
// Apply the java-library plugin to add support for Java Library
id 'java-library'
repositories {
mavenLocal()
mavenCentral()
}

// In this section you declare the dependencies for your production and test code
dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath.
//api 'org.apache.commons:commons-math3:3.6.1'
// The production code uses the SLF4J logging API at compile time
implementation 'org.slf4j:slf4j-api:1.7.21'

// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation name: 'unix'
implementation name: 'libmatthew-java-0.8'
implementation name: 'dbus-java-2.7'

// Use JUnit test framework
testImplementation 'junit:junit:4.12'
}

// In this section you declare where to find the dependencies of your project
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
flatDir { dirs 'libs' }
implementation fileTree(dir: 'libs', include: ['*.jar'])
}

jar {
Expand All @@ -42,3 +32,9 @@ jar {
'Implementation-Version': project.version)
}
}

task (runExample, dependsOn: 'classes', type: JavaExec) {
main = 'example.ExampleMain'
systemProperty "java.library.path", "/usr/lib/jni"
classpath = sourceSets.test.runtimeClasspath
}
16 changes: 16 additions & 0 deletions example.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="tango"/>
<allow send_destination="tango"/>
<allow send_destination="org.bluez"/>
</policy>
<policy at_console="true">
<allow own="tango"/>
<allow send_destination="tango"/>
<allow send_destination="org.bluez"/>
</policy>
<policy context="default">
<deny send_destination="tango"/>
</policy>
</busconfig>
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
112 changes: 69 additions & 43 deletions src/main/java/it/tangodev/ble/BleApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.List;
import java.util.Map;

import it.tangodev.utils.BleAdapter;
import org.bluez.GattApplication1;
import org.bluez.GattManager1;
import org.bluez.LEAdvertisingManager1;
Expand Down Expand Up @@ -33,10 +34,12 @@ public class BleApplication implements GattApplication1 {
public static final String BLUEZ_ADAPTER_INTERFACE = "org.bluez.Adapter1";
public static final String BLUEZ_GATT_INTERFACE = "org.bluez.GattManager1";
public static final String BLUEZ_LE_ADV_INTERFACE = "org.bluez.LEAdvertisingManager1";

public static final String ADDRESS = "Address";

private List<BleService> servicesList = new ArrayList<BleService>();
private String path;
private String adapterPath;
private String path = null;
private BleAdapter bleAdapter;
private BleService advService;
private BleAdvertisement adv;
private String adapterAlias;

Expand Down Expand Up @@ -74,19 +77,21 @@ public BleApplication(String path, BleApplicationListener listener) {
*/
public void start() throws DBusException, InterruptedException {
this.dbusConnection = DBusConnection.getConnection(DBusConnection.SYSTEM);

adapterPath = findAdapterPath();
if(adapterPath == null) { throw new RuntimeException("No BLE adapter found"); }

Properties adapterProperties = (Properties) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, adapterPath, Properties.class);
bleAdapter = findAdapterPath();
if (bleAdapter == null) {
throw new RuntimeException("No BLE adapter found");
}

Properties adapterProperties = (Properties) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, bleAdapter.getPath(), Properties.class);
adapterProperties.Set(BLUEZ_ADAPTER_INTERFACE, "Powered", new Variant<Boolean>(true));
if(adapterAlias != null) {
adapterProperties.Set(BLUEZ_ADAPTER_INTERFACE, "Alias", new Variant<String>(adapterAlias));
}
GattManager1 gattManager = (GattManager1) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, adapterPath, GattManager1.class);
LEAdvertisingManager1 advManager = (LEAdvertisingManager1) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, adapterPath, LEAdvertisingManager1.class);

GattManager1 gattManager = (GattManager1) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, bleAdapter.getPath(), GattManager1.class);

LEAdvertisingManager1 advManager = (LEAdvertisingManager1) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, bleAdapter.getPath(), LEAdvertisingManager1.class);

if (!adv.hasServices()) {
updateAdvertisement();
Expand All @@ -108,11 +113,13 @@ public void start() throws DBusException, InterruptedException {
* @throws InterruptedException
*/
public void stop() throws DBusException, InterruptedException {
if(adapterPath == null) { return; }
GattManager1 gattManager = (GattManager1) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, adapterPath, GattManager1.class);
LEAdvertisingManager1 advManager = (LEAdvertisingManager1) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, adapterPath, LEAdvertisingManager1.class);

if(adv != null) {
if (bleAdapter == null) {
return;
}
GattManager1 gattManager = (GattManager1) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, bleAdapter.getPath(), GattManager1.class);
LEAdvertisingManager1 advManager = (LEAdvertisingManager1) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, bleAdapter.getPath(), LEAdvertisingManager1.class);

if (adv != null) {
advManager.UnregisterAdvertisement(adv);
}
gattManager.UnregisterApplication(this);
Expand All @@ -133,12 +140,13 @@ protected void initInterfacesHandler() throws DBusException {
@Override
public void handle(InterfacesAdded signal) {
Map<String, Variant> iamap = signal.getInterfacesAdded().get(BLUEZ_DEVICE_INTERFACE);
if(iamap != null) {
Variant<String> address = iamap.get("Address");
System.out.println("Device address: " + address.getValue());
System.out.println("Device added path: " + signal.getObjectPath().toString());
if (iamap != null) {
Variant<String> address = iamap.get(ADDRESS);
String path = signal.getObjectPath().toString();
hasDeviceConnected = true;
if(listener != null) { listener.deviceConnected(); }
if (listener != null) {
listener.deviceConnected(path, address.getValue());
}
}
}
};
Expand All @@ -148,10 +156,12 @@ public void handle(InterfacesAdded signal) {
public void handle(InterfacesRemoved signal) {
List<String> irlist = signal.getInterfacesRemoved();
for (String ir : irlist) {
if(BLUEZ_DEVICE_INTERFACE.equals(ir)) {
System.out.println("Device Removed path: " + signal.getObjectPath().toString());
if (BLUEZ_DEVICE_INTERFACE.equals(ir)) {
String path = signal.getObjectPath().toString();
hasDeviceConnected = false;
if(listener != null) { listener.deviceDisconnected(); }
if (listener != null) {
listener.deviceDisconnected(path);
}
}
}
}
Expand Down Expand Up @@ -192,26 +202,36 @@ public BleAdvertisement getAdvertisement() {

/**
* Search for a Adapter that has GattManager1 and LEAdvertisement1 interfaces, otherwise return null.
* @return
* @throws DBusException
* @return BleAdapter based on the map stored in the D-Bus Managed object org.bluez.Adapter1
* @throws DBusException if there is an error communicating with BlueZ over D-Bus
*/
private String findAdapterPath() throws DBusException {
ObjectManager bluezObjectManager = (ObjectManager) dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, "/", ObjectManager.class);
if(bluezObjectManager == null) { return null; }
public BleAdapter findAdapterPath() throws DBusException {
ObjectManager bluezObjectManager = dbusConnection.getRemoteObject(BLUEZ_DBUS_BUSNAME, "/", ObjectManager.class);
if (bluezObjectManager == null) {
return null;
}

Map<Path, Map<String, Map<String, Variant>>> bluezManagedObject = bluezObjectManager.GetManagedObjects();
if(bluezManagedObject == null) { return null; }

if (bluezManagedObject == null) {
return null;
}

for (Path path : bluezManagedObject.keySet()) {
Map<String, Map<String, Variant>> value = bluezManagedObject.get(path);
boolean hasGattManager = false;
boolean hasAdvManager = false;

for(String key : value.keySet()) {
if(key.equals(BLUEZ_GATT_INTERFACE)) { hasGattManager = true; }
if(key.equals(BLUEZ_LE_ADV_INTERFACE)) { hasAdvManager = true; }

if(hasGattManager && hasAdvManager) { return path.toString(); }

for (Map.Entry<String, Map<String, Variant>> entry : value.entrySet()) {
if (entry.getKey().equals(BLUEZ_GATT_INTERFACE)) {
hasGattManager = true;
}
if (entry.getKey().equals(BLUEZ_LE_ADV_INTERFACE)) {
hasAdvManager = true;
}
if (hasGattManager && hasAdvManager) {
return new BleAdapter(path, value.get("org.bluez.Adapter1"));
}

}
}

Expand Down Expand Up @@ -245,15 +265,17 @@ private void unexport() throws DBusException {
}
dbusConnection.unExportObject(path);
}

@Override
public boolean isRemote() { return false; }

public boolean isRemote() {
return false;
}

@Override
public Map<Path, Map<String, Map<String, Variant>>> GetManagedObjects() {
System.out.println("Application -> GetManagedObjects");
Map<Path, Map<String, Map<String, Variant>>> response = new HashMap<Path, Map<String,Map<String,Variant>>>();

Map<Path, Map<String, Map<String, Variant>>> response = new HashMap<Path, Map<String, Map<String, Variant>>>();
for (BleService service : servicesList) {
response.put(service.getPath(), service.getProperties());
for (BleCharacteristic characteristic : service.getCharacteristics()) {
Expand All @@ -275,4 +297,8 @@ private void updateAdvertisement() {
}
}
}
}
public BleAdapter getBleAdapter() {
return bleAdapter;
}

}
4 changes: 2 additions & 2 deletions src/main/java/it/tangodev/ble/BleApplicationListener.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package it.tangodev.ble;

public interface BleApplicationListener {
public void deviceConnected();
public void deviceDisconnected();
public void deviceConnected(String path, String address);
public void deviceDisconnected(String path);
}
Loading