Commit 2743ae2f authored by ttschol's avatar ttschol
Browse files

Added initial bleclient files, READMEs, gitlab-ci files and protocol for TimeFlip

parent aa7d50bd
Pipeline #41577 passed with stages
in 47 seconds
*.idea
*.vscode
image: docker.uibk.ac.at:443/csat2410/skeleton-bleclient
stages:
- Static Analysis
- Test
before_script:
- mvn -version
- java -version
- echo $JAVA_HOME
- echo $MAVEN_HOME
- python3 --version
- pip3 --version
- echo $CI_PROJECT_DIR
# See: https://pmd.github.io/
pmd:
stage: Static Analysis
allow_failure: true
script:
- cd bleclient
- /opt/pmd-bin-6.31.0/bin/run.sh pmd -d src/main/java/ -f text -R rulesets/java/quickstart.xml -cache pmd_cache.txt
bleclient-test:
stage: Test
script:
- mkdir bleclient-test
- cd bleclient
- mvn clean install
- JACOCO_SCORE=$(awk -F, '{ instructions += $4 + $5; covered += $5 } END { printf "%.2f\n", 100*covered/instructions }' target/site/jacoco/jacoco.csv)
- anybadge --label=jacoco-coverage --file=target/site/jacoco/jacoco.svg --value=$JACOCO_SCORE coverage
- mvn dependency:tree -DoutputFile="$CI_PROJECT_DIR/bleclient-test/mvn_dependencies.txt"
- cp -r target/site/jacoco $CI_PROJECT_DIR/bleclient-test/jacoco
- cp -r target/apidocs $CI_PROJECT_DIR/bleclient-test/apidocs
artifacts:
paths:
- bleclient-test
# skeleton-bleclient
# Java und Bluetooth Low Energy auf dem Raspberry Pi
Installation von Bluetooth auf dem Raspberry Pi und Verwendung von Java, um mit einem
bluetooth-fähigen Gerät zu kommunizieren.
## Voraussetzungen
### Grundsätzliche Einstellungen
- `Raspberry Pi OS Lite` auf SD-Karte von Raspberry Pi geflashed
- Anmeldung mit Benutzername `pi` und Passwort `raspberry` und Änderung des Passworts mit `passwd`
- Bluetooth and WLAN sind aktiviert (weder `hard` noch `soft`-blocked)
sudo rfkill list all
- Aktivierung von z.B. WiFi mit (sollte WiFi der erste Eintrag sein):
sudo rfkill unblock 0
### Verwendung von WiFI
- Setzen des WiFi-Landes mit:
sudo raspi-config nonint do_wifi_country AT
sudo raspi-config nonint get_wifi_country
- Siehe: [Setting up a wireless LAN via the command line](https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md)
- Überprüfung, ob man mit dem richtigen Netzwerk verbunden ist:
iw wlan0 link
- Überprüfung, ob Internetverbindung besteht mit:
ping google.com
### Verbindung mit Raspberry Pi
- SSH wurde aktiviert (Ausgabe soll 0 sein):
sudo raspi-config nonint get_ssh
- Aktivierung von SSH mit:
sudo raspi-config nonint do_ssh 0
- Hostname des Raspberry Pi ist bekannt:
hostname -I
### Optional: Private/Public Key Authentifizierung
Siehe: [Passwordless SSH access](https://www.raspberrypi.org/documentation/remote-access/ssh/passwordless.md)
So vermeidet man beim Verbinden zum Raspberry Pi jedes Mal das Passwort einzugeben.
## 1) Installation
Mit Raspberry Pi verbinden:
ssh pi@<RASPBERRY_IP_ADDRESS>
### a) Initiale Packages
Auf den neuesten Stand bringen:
sudo apt update
sudo apt upgrade
Essentielle Tools installieren:
sudo apt install git
sudo apt install cmake
### b) Maven und JDK
Installation der JDK 1.8:
sudo apt install openjdk-8-jdk
Sicherstellen, dass tatsächlich JDK 1.8 verwendet wird:
sudo update-alternatives --config java
Version überprüfen:
java -version
Sicherstellen, dass sich in `usr/lib/jvm` nun `java-8-openjdk-armhf` befindet:
sudo find / -name "java"
Editieren von `bashrc`:
sudo nano ~/.bashrc
Es soll die Zeile `export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-armhf/` am Ende hinzugefügt werden und gespeichert werden.
Nun müssen wir das Terminal neu laden, um zu überprüfen, ob die Änderungen wirksam waren – notwendig für nächste Schritte:
bash
echo $JAVA_HOME
Maven installieren:
sudo apt install maven
### c) Installation von BlueZ 5.47
Installation der Build-Tools:
sudo apt install libglib2.0-dev libdbus-1-dev libudev-dev libical-dev libreadline-dev
Download des BlueZ Source-Codes (in geeignetem Directory):
wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.47.tar.xz
Das tar Archiv extrahieren und in den Ordner gehen:
tar -xf bluez-5.47.tar.xz && cd bluez-5.47
Konfigurieren des BlueZ Projekts:
./configure --prefix=/usr --mandir=/usr/share/man --sysconfdir=/etc --localstatedir=/var
BlueZ builden:
make
sudo make install
#### BlueZ Installation überprüfen
Der BlueZ Start-up Service soll nun auf das neu gebuildete BlueZ zeigen:
cat /lib/systemd/system/bluetooth.service
Dort sollte man die Zeile `ExecStart=/usr/libexec/bluetooth/bluetoothd` vorfinden.
Ausgabe der BlueZ Version – sollte nun `5.47` sein:
/usr/libexec/bluetooth/bluetoothd --version
#### Erlaubnis für BlueZ hinzufügen
Damit BlueZ Zugriff auf die Bluetooth-Gruppe hat, müssen wir eine eigene Erlaubnis hinzufügen. Dafür
editiert man die BlueZ DBus Konfiguration:
sudo nano /etc/dbus-1/system.d/bluetooth.conf
Anschließend fügt man nur die Policy für die Gruppe `bluetooth` hinzu:
```shell
<busconfig>
<policy user="root">
...
</policy>
<policy group="bluetooth">
<allow send_destination="org.bluez"/>
</policy>
...
</busconfig>
```
#### Zusätzliche Konfiguration und neu starten
OpenHab User zur Bluetooth-Gruppe hinzufügen (wir benötigten zwar OpenHab nicht, aber zur Vollständigkeit):
sudo adduser --system --no-create-home --group --disabled-login openhab
sudo usermod -a -G bluetooth openhab
Service Definitionen neu laden:
sudo systemctl daemon-reload
BlueZ neu starten:
sudo systemctl restart bluetooth
Überprüfen, ob Bluetooth-Service aktiv ist, die richtige Version läuft (`5.47`) und es keine Fehler gibt:
sudo systemctl status bluetooth
### d) Installation von tinyb
Installation der Abhängigkeiten von `tinyb`:
sudo apt install graphviz
sudo apt install doxygen
Klonen von tinyb (an geeigneter Stelle) und in den Ordner gehen:
git clone https://github.com/intel-iot-devkit/tinyb.git && cd tinyb
Ordner `build` erstellen und hineingehen:
mkdir build
cd build
Builden von `tinyb` mit `cmake` (`-E` steht für experimental und stellt sicher, dass `JAVA_HOME` verwendet wird, `cmake ..` generiert das Makefile im aktuellen Verzeichnis basierend auf `CMakeLists.txt` im parent-Verzeichnis, der Prefix `/usr/` stellt sicher, dass sich die native Libraries `libjavatinyb.so` und `libtinyb.so` im Java Library Path befinden):
sudo -E cmake -DBUILDJAVA=ON -DCMAKE_INSTALL_PREFIX=/usr ..
Ausführen von `make` und `make install`:
sudo make
sudo make install
## 2) Ausführen
Die Ausführung ist eher umständlich, da wir `tinyb.jar` nicht nur zur Compile-Zeit, sondern auch dynamisch zur Laufzeit laden müssen. Für diese Bluetooth-Library ist dies leider notwendig. Siehe auch: [Java* for Bluetooth® Low Energy Applications](https://web.archive.org/web/20190414051809/https:/software.intel.com/en-us/java-for-bluetooth-le-apps)
Sollten alle Befehle erfolgreich sein, dann können wir das Beispiel-Maven-Programm `bleclient` ausführen.
Dafür gehen wir in das Verzeichnis `bleclient` und führen das Programm basierend auf dem dort liegendem [README.md](bleclient/README.md) aus.
## 3) Optional: Installation z.B. auf Ubuntu 18.04/20.04
Führen Sie die Schritte `1a` und `1b` aus:
- Für `1b` ändert sich, dass wir statt `java-8-openjdk-armhf` die `java-8-openjdk-armhf` verwenden
Führen Sie Schritt `1c` aus:
- Sollte es bei `make` zum Fehler `error: ‘SIOCGSTAMP’ undeclared (first use in this function)` kommen, dann includen Sie `#include <linux/sockios.h>` in den nötigen Dateien z.B. `bluez-5.47/tools/rctest.c` und `bluez-5.47/tools/l2test.c`
- Dieses Problem scheint ab Ubuntu 20.04 aufzutreten. In Ubuntu 18.04 kann es sein, dass dieses Problem nicht auftritt.
Führen Sie die restlichen Schritte aus. Fertig.
Hinweis: Passen Sie beim Ausführen von Updaten/Upgrades auf. Es kann sein, dass dann Bluetooth upgegradet wird. Dies wollen wir vermeiden. Wir wollen maximal Version `5.47` verwenden.
## Fragen und Antworten
### Was mache ich, wenn irgendetwas in der Installation schief läuft?
- Sicherstellen, dass alle Befehle richtig ausgeführt wurden.
- Generell kann es helfen, noch einmal alle Befehle von vorne auszuführen.
### Was wenn der der Java BluetoothManager eine Exception wirf (z.B. NullPointerException)?
- Sicherstellen, dass das gebuildete `tinyb.jar`zur Verfügung steht
- `tinyb.jar` muss zur Laufzeit als Parameter übergeben werden z.B. `–cp target/<JAR_FILE>:./lib/tinyb.jar:./target/dependencies/*`
### Was wenn ich beim Ausführen ein Problem mit der nativen API Version bekomme?
- Wahrscheinlich wurde die `tinyb.jar` nicht korrekt zur Laufzeit geladen
- Sicherstellen, dass sie richtig geladen wird z.B. `–cp target/<JAR_FILE>:./lib/tinyb.jar:./target/dependencies/*`
### Wieso kann ich das Programm nicht mit -jar ausführen?
- Entweder man verwendet `-cp` für Classpath oder `-jar`, aber nicht beides
- Für `-jar` benötigt man weiters auch noch eine Manifest-Datei (diese müsste man in Bezug auf das `tinyb.jar` auch konfigurieren)
### Wieso können die native Libraries nicht gefunden werden?
- Sollte grundsätzlich kein Problem sein, wenn man der Anleitung gefolgt hat
- Durch `-DCMAKE_INSTALL_PREFIX=/usr` sollten diese korrekt gesetzt sein
- Siehe auch bei Installation `tinyb/build/install_manifest.txt`
- Mit `java -cp` korrekt ausführen und nicht mit `-jar`
- Wenn es immer noch nicht möglich ist, dann muss man wirklich sicherstellen, dass sich `libjavatinyb.so` und `libtinyb.so` tatsächlich im Java Library Path befinden.
- Prinzipiell könnte man diese Dateien `tinyb/build/java/jni/libjavatinyb.so` und `tinyb/build/src/libtinby.so` auch direkt nach `/usr/lib` kopieren
### Wieso bekomme ich eine BluetoothException mit Timeout was reached?
- Sicherstellen, dass sich der TimeFlip tatsächlich in Reichweite befindet
- Sicherstellen, dass die Batterie richtig im TimeFlip ist
### Wieso bekomme ich Exceptions beim Auslesen von Charakteristiken?
- Sicherstellen, dass das Passwort richtig vorher an den TimeFlip geschrieben wurde, dann sollte man alle Charakteristiken auslesen können
- Es kann sein, dass man versehentlich das Passwort über die TimeFlip App gesetzt hat. Wenn man die Batterie kurz herausnimmt und noch einmal einsetzt, dann sollte das Passwort zurückgesetzt sein.
### Was mache ich, wenn das bleclient Programm keinen TimeFlip ausgibt?
- Sicherstellen, dass der TimeFlip eingeschalten ist (z.B. Überprüfung mit Handy-App wie `nRF Connect`)
- Am besten notiert man sich die UUID des TimeFlips
- Sicherstellen, dass der TimeFlip mit keinem anderen Gerät gekoppelt ist
- Entkoppeln von Bluetooth, TimeFlip App, etc.
- Theoretisch ist es möglich, dass aus irgendeinem Grund das Passwort auf dem TimeFlip z.B. mit der TimeFlip App gesetzt wurde. In diesem Fall sollte man die Batterie kurz entfernen und wieder reinstecken
## Links
* [Raspberry Pi Bluetooth Manager TinyB - Building bluez 5.47 from sources](https://github.com/sputnikdev/bluetooth-manager-tinyb)
* [TinyB Bluetooth LE Library](https://github.com/intel-iot-devkit/tinyb)
* [Raspberry Pi Installation of TinyB (Note: do not install bluez)](http://www.martinnaughton.com/2017/07/install-intel-tinyb-java-bluetooth.html)
* [Java for Bluetooth LE applications](https://www.codeproject.com/Articles/1086361/Java-for-Bluetooth-LE-applications)
* [TinyB Java examples (HelloTinyB.java, etc.)](https://github.com/intel-iot-devkit/tinyb/tree/master/examples/java)
* [Non-interactive raspi-config interface](https://github.com/raspberrypi-ui/rc_gui/blob/master/src/rc_gui.c#L23-L70)
/target/
.idea/
.settings/
.classpath
.project
*.iml
# Bluetooth Low Energy auf dem Raspberry Pi
### Voraussetzungen
* Die Installation von `tinyb` war erfolgreich
* Ein bluetooth-fähiges Gerät befindet sich in der Nähe
* Im Ordner `lib` befindet sich die gebuildete Datei `tinyb.jar` von der Installation (`tinyb/build/java/tinyb.jar`)
## Builden
Zuerst müssen wir das JAR builden, damit wir `bleclient.jar` im `target`-Verzeichnis bekommen.
Zusätzlich werden alle Dependencies im `pom.xml` in `target/dependencies` abgelegt. Wir erreichen dies durch die Plugins
`maven-install-plugin` und `maven-dependency-plugin`.
### Builden
mvn clean package -Dmaven.test.skip=true -Dmaven.javadoc.skip=true
### Builden mit Tests und Javadoc
mvn clean install
## Ausführen
Ausführung des Programms `bleclient.jar` mit `tinyb.jar` und den Dependencies in `target/dependencies/*`.
Auch Angabe des `fully-qualified name` der auszuführenden Java-Klasse:
sudo java -cp target/bleclient.jar:./lib/tinyb.jar:./target/dependencies/* at.qe.skeleton.bleclient.Main
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>at.qe.skeleton</groupId>
<artifactId>bleclient</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- TinyB -->
<dependency>
<groupId>org.sputnikdev</groupId>
<artifactId>bluetooth-manager-tinyb</artifactId>
<version>1.3.3</version>
</dependency>
<!-- Tests -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.7.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- Remove version from JAR file to keep consistent to keep execution consistent even when version changes -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- Install TinyB library into local maven repository -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>install-tinyb</id>
<phase>package</phase>
<goals>
<goal>install-file</goal>
</goals>
<configuration>
<file>lib/tinyb.jar</file>
<groupId>intel-iot-devkit</groupId>
<artifactId>tinyb</artifactId>
<version>0.6.0</version>
<packaging>jar</packaging>
</configuration>
</execution>
</executions>
</plugin>
<!-- Copy all dependencies into separate directory -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/dependencies</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<!-- Enable jacoco analysis -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<forkMode>once</forkMode>
<argLine>
@{coverageAgent}
</argLine>
</configuration>
</plugin>
<!-- Test and generate coverage with Jacoco -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<configuration>
<propertyName>coverageAgent</propertyName>
</configuration>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Generate javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package at.qe.skeleton.bleclient;
import tinyb.BluetoothDevice;
import tinyb.BluetoothException;
import tinyb.BluetoothManager;
import java.util.List;
// TODO: use logging instead of System.out/System.err
/**
* Entry point for program to search for Bluetooth devices and communicate with them
*/
public final class Main {
private Main() {
}
/**
* This program should connect to TimeFlip devices and read the facet characteristic exposed by the devices
* over Bluetooth Low Energy.
*
* @param args the program arguments
* @see <a href="https://github.com/DI-GROUP/TimeFlip.Docs/blob/master/Hardware/BLE_device_commutication_protocol_v3.0_en.md" target="_top">BLE device communication protocol v3.0</a>
*/
public static void main(String[] args) {
BluetoothManager manager = BluetoothManager.getBluetoothManager();
final String findDeviceName = "TimeFlip";
final boolean discoveryStarted = manager.startDiscovery();
System.out.println("The discovery started: " + (discoveryStarted ? "true" : "false"));
try {
manager.stopDiscovery();
} catch (BluetoothException e) {
System.err.println("Discovery could not be stopped.");
}
System.out.println("All found devices:");
manager.getDevices().forEach(d -> System.out.println(d.getAddress() + " - " + d.getName() + " (" + d.getRSSI() + ")"));
List<BluetoothDevice> filteredDevices = TinybUtil.getFilteredDevices(manager, findDeviceName);
if (filteredDevices.isEmpty()) {
System.err.println("No " + findDeviceName + " devices found during discovery.");
System.exit(-1);
}
System.out.println("Found " + filteredDevices.size() + " " + findDeviceName + " device(s).");
for (BluetoothDevice device : filteredDevices) {
System.out.println("Found " + findDeviceName + " device with address " + device.getAddress() + " and RSSI " +
device.getRSSI());
if (device.connect()) {
System.out.println("Connection established");
// TODO: read from device
device.disconnect();
} else {
System.out.println("Connection not established - trying next one");
}
}
}
}
package at.qe.skeleton.bleclient;
import com.google.common.base.Preconditions;
import tinyb.BluetoothDevice;
import tinyb.BluetoothManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
// TODO: use logging instead of System.out
/**
* An utility class for bluetooth low energy devices
*/
public final class TinybUtil {
public static final int MIN_RSSI_ALLOWED = -80;
private TinybUtil() {
}
/**
* Filter Bluetooth devices based on searchDevice
* <p>
* If the signal strength is too low then we should not connect to the device. The communication may
* be unstable.
*
* @param manager the Bluetooth manager from which we get the Bluetooth devices
* @param searchDevice the devices we want to search for
* @return filtered bluetooth devices
*/
public static List<BluetoothDevice> getFilteredDevices(final BluetoothManager manager, final String searchDevice) {
Preconditions.checkNotNull(manager, "Precondition violation - argument 'manager' must not be NULL!");
Preconditions.checkNotNull(searchDevice, "Precondition violation - argument 'searchDevice' must not be NULL!");
List<BluetoothDevice> devices = new ArrayList<>();
for (BluetoothDevice device : manager.getDevices()) {
if (device.getName().toLowerCase(Locale.ROOT).contains(searchDevice)) {
final int rssi = device.getRSSI();
if (rssi == 0) {
System.out.println(searchDevice + " with address " + device.getAddress() + " has no signal.");
} else if (rssi < MIN_RSSI_ALLOWED) {
System.out.println(searchDevice + " with address" + device.getAddress() + " has a very low signal ("
+ rssi + ")");
} else {
devices.add(device);
}
}
}
return devices;
}
}
package at.qe.skeleton.bleclient;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;