Depreciation Notice - This project is no longer maintained in favor of the ESP32 variant. Pull requests and questions are still welcome.
This project aims to be a BLE to MQTT bridge, i.e. expose BLE GATT characteristics as MQTT topics for bidirectional communication. It relies on the BlueZ DBus API and as such is supported on Linux only.
For example, if a device with a MAC address of A0:E6:F8:50:72:53
exposes the
0000180f-0000-1000-8000-00805f9b34fb service
(Battery Service) which includes the
00002a19-0000-1000-8000-00805f9b34fb characteristic
(Battery Level), the A0:E6:F8:50:72:53/BatteryService/BatteryLevel
MQTT topic is published with a value representing the battery level.
In order to set a GATT value, publish a message to a writable characteristic
using the above format suffixed with /Set
. Note that values are byte arrays so
writing a 64-bit value would look like 10,231,32,24
.
While the basic configuration file provided in the repository (config.json) should be enough for most users, it is possible to tweak it a bit to fit one's specific needs.
The mqtt
section below includes the following entries:
{
"mqtt": {
"server": {
"host": "127.0.0.1",
"port": 1883
},
"publish": {
"retain": true
},
"topics" :{
"device_name": "Address",
"set_suffix": "/Set"
}
}
}
server
- define which MQTT broker the application should connect topublish
- configuration for publishing topics. This object is passed as-is to the mqtt.publish() methodtopics
device name
- define which attribute of a device (as exposed by Bluez) should be used to identify it in the MQTT topicset_suffix
- Which suffix should be added to the MQTT value topic in order to write a new value to the characteristic. Set this to an empty string if you wish to use the same topic for reading and writing
The ble
section of the configuration file includes the following default
configuration:
{
"ble": {
"services": { },
"characteristics": { }
}
}
-
services
- add an additional service or override an existing definition to the ones grabbed automatically on first run from http://www.bluetooth.org. Each service can include aname
field which will be used in the MQTT topic instead of its UUID. For example:"services": { "00002f00-0000-1000-8000-00805f9b34fb": { "name": "Relay Service" }, }
-
characteristics
- add an additional characteristic or override an existing definition to the ones grabbed automatically on first run from http://www.bluetooth.org. Each characteristic can include aname
field which will be used in the MQTT topic instead of its UUID, atypes
array defining how to parse the byte array reflecting the characteristic's value and apoll
value (in seconds) for the application to poll the BLE device for a new value. For example:"characteristics": { "00002a19-0000-1000-8000-00805f9b34fb": { "//": "Poll the battery level characteristic every day", "poll": 86400 }, "00002f01-0000-1000-8000-00805f9b34fb": { "name": "Relay State", "types": [ "boolean" ] } }
-
whitelist
/blacklist
- An array of strings or regular expressions used to match MAC addresses of devices. Ifwhitelist
is used, only devices with a MAC address matching one of the entries will be connected while ifblacklist
is used, only devices that do not match any entry will be connected to."whitelist": [ "A0:E6:F8:.*" ]
-
passkeys
- An object containing the passkey (number 000000~999999) used for out-of-band authorization. Each entry is the MAC address of the BLE device and the value is the passkey to use."passkeys": { "B0:B4:48:D3:63:98": 123456 }
This app requires node version >= 4.3.2 (need support for arrow functions) as well as a fairly recent version of Bluez (>= 5.40).
Note that you can probably point your apt sources to stretch/testing to get newer versions of these packages. I, personally, haven't tried that yet
My personal setup is a Raspberry Pi 3 utilizing its built-in Bluetooth radio. I needed to build a newer version of Bluez and needed it to be a Debian package since a different package (pi-bluetooth, which creates the HCI device) depends on it. To overcome this, I ran the following:
# Get dependencies
sudo apt-get update
sudo apt-get install -y libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev \
libical-dev libreadline-dev checkinstall
# Compile Install Bluez 5.45
mkdir -p ~/Downloads
wget -O ~/Downloads/bluez-5.45.tar.xz http://www.kernel.org/pub/linux/bluetooth/bluez-5.45.tar.xz
mkdir -p ~/code
cd ~/code
tar -xvf ~/Downloads/bluez-5.45.tar.xz
cd bluez-5.45
# Allow tabs to be tabs (for patches)
bind '\C-i:self-insert'
patch -p1 << EOF
--- a/tools/hciattach.c
b/tools/hciattach.c
@@ -1236,7 1236,7 @@
{
struct uart_t *u = NULL;
int detach, printpid, raw, opt, i, n, ld, err;
- int to = 10;
int to = 30;
int init_speed = 0;
int send_break = 0;
pid_t pid;
--- a/tools/hciattach_bcm43xx.c
b/tools/hciattach_bcm43xx.c
@@ -43,7 43,7 @@
#include "hciattach.h"
#ifndef FIRMWARE_DIR
-#define FIRMWARE_DIR "/etc/firmware"
#define FIRMWARE_DIR "/lib/firmware"
#endif
#define FW_EXT ".hcd"
@@ -366,11 366,8 @@
return -1;
if (bcm43xx_locate_patch(FIRMWARE_DIR, chip_name, fw_path)) {
- fprintf(stderr, "Patch not found, continue anyway\n");
fprintf(stderr, "Patch not found for %s, continue anyway\n", chip_name);
} else {
- if (bcm43xx_set_speed(fd, ti, speed))
- return -1;
-
if (bcm43xx_load_firmware(fd, fw_path))
return -1;
@@ -380,6 377,7 @@
return -1;
}
sleep(1);
if (bcm43xx_reset(fd))
return -1;
}
--- a/src/bluetooth.conf
b/src/bluetooth.conf
@@ -34,6 34,10 @@
<allow send_destination="org.bluez"/>
</policy>
<policy group="bluetooth">
<allow send_destination="org.bluez"/>
</policy>
<policy context="default">
<deny send_destination="org.bluez"/>
</policy>
--- a/src/bluetooth.service.in
b/src/bluetooth.service.in
@@ -6,7 6,7 @@
[Service]
Type=dbus
BusName=org.bluez
-ExecStart=@libexecdir@/bluetoothd
ExecStart=@libexecdir@/bluetoothd -E
NotifyAccess=main
#WatchdogSec=10
#Restart=on-failure
EOF
# Re-enable tabs
bind '\C-i:complete'
./configure --disable-cups --disable-obex --enable-deprecated --prefix=/usr --libexecdir=/usr/lib --localstatedir=/var/lib/bluetooth/
make -j 4
sudo checkinstall -y --nodoc [email protected]
To install Node, I used the following:
# Install Node
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs
Raspbian Jesse uses systemd as its init process, so I created a service file for
it. Make sure to add your user to the bluetooth
group so you can run this
application without running as root.
cat << EOF > ble2mqtt@$USER.service
[Unit]
Description=BLE2MQTT Bridge for %i
After=network.target bluetooth.service
[Service]
Type=simple
WorkingDirectory=/home/%i/code/ble2mqtt
ExecStart=/usr/bin/npm start
User=%i
SendSIGKILL=no
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
sudo mv ble2mqtt@$USER.service /lib/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable ble2mqtt@$USER.service
sudo systemctl start ble2mqtt@$USER.service
- Add configuration file:
MQTT settingsSingle/Split topic for get/set- MQTT topic prefix (to distinguish between different instances of the app)
Error handling:What happens when an adapter/device is disconnected? Do we need to cleanup anything? What happens to events on removed devices?
- Pretty names (should be configurable):
Allow using different properties as device name- Listen on changes in the property used for the device name as if it changes, topic names (both published and subscribed) need to be updated
Use service/characteristic name instead of UUIDExtendable via configuration file
Pretty values (convert byte array to Boolean, String, etc.):Configuration file can define custom characteristics
- Refactoring
- Create a separate NPM module out of the BlueZ code
- Lots of similar code copy-pasted, we can do better
SecuritySupport pairing via AgentManager1 API