espruino / EspruinoHub

A BLE -> MQTT bridge for Raspberry Pi and other Embedded devices
Other
238 stars 62 forks source link

EspruinoHub

A BLE -> MQTT bridge for Raspberry Pi and other Embedded devices for Espruino and Puck.js

Setting up

Ideally use a Raspberry Pi 3 or Zero W, as these have Bluetooth LE on them already. However the BLE USB dongles mentioned in the Puck.js Quick Start guide should work.

Get Raspbian running on your Raspberry Pi

Installation of everything (EspruinoHub, Node-RED, Web IDE)

These instructions install up to date Node.js and Node-RED - however it can take a while! If you just want EspruinoHub and the IDE, see the next item.

sudo apt-get update
# OPTIONAL: Update everything to latest versions
sudo apt-get upgrade -y
# Get required packages
sudo apt-get install -y build-essential python-rpi.gpio nodejs nodered git-core
# OPTIONAL: Install a modern version of nodejs and nodered
# Not recommended - The Pi's supplied Node.js version is more than good enough
# bash <(curl -sL https://raw.githubusercontent.com/node-red/raspbian-deb-package/master/resources/update-nodejs-and-nodered)
# Get dependencies
sudo apt-get install -y mosquitto mosquitto-clients bluetooth bluez libbluetooth-dev libudev-dev
# Auto start Node-RED
sudo systemctl enable nodered.service
# Start nodered manually this one time (this creates ~/.node-red)
sudo systemctl start nodered.service
# wait for the ~/.node-red directory to get created...
# Install the Node-RED UI
cd ~/.node-red && npm install node-red-contrib-ui
# Now get EspruinoHub
cd ~/
git clone https://github.com/espruino/EspruinoHub
# Install EspruinoHub's required Node libraries
cd EspruinoHub
npm install

# Give Node.js access to Bluetooth
sudo setcap cap_net_raw+eip $(eval readlink -f `which node`)

# You may need to run the setcap line above again if you update Node.js

You can now type ./start.sh to run EspruinoHub, but it's worth checking out the Auto Start section to see how to get it to run at boot.

Installation of EspruinoHub and Web IDE

# Install Node, Bluetooth, etc
sudo apt-get update
# OPTIONAL: Update everything to latest versions
sudo apt-get upgrade -y
# Get required packages
sudo apt-get install -y git-core nodejs npm build-essential mosquitto mosquitto-clients bluetooth bluez libbluetooth-dev libudev-dev
# Now get EspruinoHub
git clone https://github.com/espruino/EspruinoHub
# Install EspruinoHub's required Node libraries
cd EspruinoHub
npm install

# Give Node.js access to Bluetooth
sudo setcap cap_net_raw+eip $(eval readlink -f `which node`)

# You may need to run the setcap line above again if you update Node.js

You can now type ./start.sh to run EspruinoHub, but it's worth checking out the Auto Start section to see how to get it to run at boot.

Auto Start

There are a 2 main ways to run EspruinoHub on the Raspberry Pi.

Headless Startup

This is the normal way of running services - to configure them as a system start-up job using systemd:**

    sudo cp systemd-EspruinoHub.service /etc/systemd/system/EspruinoHub.service

and edit it as necessary to match your installation directory and user configuration. Then, to start it for testing:

    sudo systemctl start EspruinoHub.service && sudo journalctl -f -u EspruinoHub

If it works, Ctrl-C to break out and enable it to start on login:

    sudo systemctl enable EspruinoHub.service

Console Startup

If you have a video output on your Pi then you can run EspruinoHub at boot - on the main display - so that you can see what it's reporting.

if [ $(tty) == /dev/tty1 ]; then
  while true; do
    EspruinoHub/start.sh
    sleep 1s
  done
fi

Notes

Uninstalling

Assuming you followed the steps above (including for 'Headless Startup') you can uninstall EspruinoHub using the following commands:

sudo systemctl stop EspruinoHub.service
sudo systemctl disable EspruinoHub.service
sudo rm /etc/systemd/system/EspruinoHub.service
sudo rm -rf ~/EspruinoHub

Run with Docker

More information how work Bluetooth in docker you can read in article "How to run containerized Bluetooth applications with BlueZ" by Thomas Huffert

Currently, espruinohub has support for multiple architectures:

Install:

docker pull ghcr.io/espruino/espruinohub

Run from the directory containing your config.json:

docker run -d -v $PWD/config.json:/data/config.json:ro --restart=always --net=host --privileged --name espruinohub ghcr.io/espruino/espruinohub

Example for docker-compose.yml

  espruinohub:
    image: ghcr.io/espruino/espruinohub
    hostname: espruinohub
    container_name: espruinohub
    privileged: true
    environment:
      - TZ=Europe/Amsterdam
      - NOBLE_HCI_DEVICE_ID=0
    network_mode: host
    volumes:
      - /home/twocolors/espruinohub:/data
    restart: unless-stopped

Manual build:

docker build -t espruino/espruinohub https://github.com/espruino/EspruinoHub.git

Usage

Once started, you then have a few options...

Status / Websocket MQTT / Espruino Web IDE

By default EspruinoHub starts a web server at http://localhost:1888 that serves the contents of the www folder. You can disable this by setting http_port to 0 in config.json.

With that server, you can:

MQTT / Node-RED

If set up, you can access Node-RED using http://localhost:1880

Once you add UI elements and click Deploy they'll be visible at http://localhost:1880/ui

The easiest way to get data is to add an MQTT listener node that requests /ble/advertise/# (# is a wildcard). This will output all information received via advertising (see 'Advertising Data' below).

For more info on available MQTT commands see the 'MQTT Bridge' section below.

Check out http://www.espruino.com/Puck.js+Node-RED for a proper introduction on using Node-RED.

MQTT Command-line

You can use the Mosquitto command-line tools to send and receive MQTT data that will make EspruinoHub do things:

# listen to all, verbose
mosquitto_sub -h localhost -t "/#" -v

# listen to any device advertising a 1809 temperature characteristic and
# output *just* the temperature
mosquitto_sub -h localhost -t "/ble/advertise/+/temp"

# Test publish
mosquitto_pub -h localhost -t test/topic -m "Hello world"

For more info on available MQTT commands see the 'MQTT Bridge' section below.

MQTT bridge

Advertising

Data that is received via bluetooth advertising will be relayed over MQTT in the following format:

You can take advantage of Espruino's manufacturer ID 0x0590 to relay JSON over Bluetooth LE advertising using the following code on an Espruino board:

var data = {a:1,b:2};
NRF.setAdvertising({},{
  showName:false,
  manufacturer:0x0590,
  manufacturerData:E.toJS(data)
});
// Note: JSON.stringify(data) can be used instead of
// E.toJS(data) to produce 'standard' JSON like {"a":1,"b":2}
// instead of E.toJS's more compact {a:1,b:2}

Assuming a device with an address of ma:c_:_a:dd:re:ss this will create the folling MQTT topics when mqtt_advertise_manufacturer_data is true in config.json:

Note that you only have 24 characters available for JSON, so try to use the shortest field names possible and avoid floating point values that can be very long when converted to a String.

Connections

You can also connect to a device using MQTT packets:

SERVICE and CHARACTERISTIC are either known names from attributes.js such as nus and nus_tx or are of the form 6e400001b5a3f393e0a9e50e24dcca9e for 128 bit uuids or abcd for 16 bit UUIDs.

After connecting, EspruinoHub will stay connected for a few seconds unless there is any activity (eg a write or ping). So you can for instance evaluate something on a Puck.js BLE UART connection with:

=> /ble/notify/c7:f9:36:dd:b0:ca/nus/nus_rx
"\x10Bluetooth.println(E.getTemperature())\n" => /ble/write/c7:f9:36:dd:b0:ca/nus/nus_tx

/ble/data/c7:f9:36:dd:b0:ca/nus/nus_rx => "23\r\n"

Once a /ble/write/DEVICE/SERVICE/CHARACTERISTIC has been executed, a /ble/written/DEVICE/SERVICE/CHARACTERISTIC packet will be sent in response.

Payload can take the following values

History

EspruinoHub contains code (libs/history.js) that subscribes to any MQTT data beginning with /ble/ and that then stores logs of the average value every minute, 10 minutes, hour and day (see config.js:history_times). The averages are broadcast over MQTT as the occur, but can also be queried by sending messages to /hist/request.

For example, an Espruino device with address f5:47:c8:0b:49:04 may broadcast advertising data with UUID 1809 (Temperature) with the following code:

setInterval(function() {
  NRF.setAdvertising({
    0x1809 : [Math.round(E.getTemperature())]
  });
}, 30000);

This is decoded into temp by attributes.js, and it sends the following MQTT packets:

/ble/advertise/f5:47:c8:0b:49:04 {"rssi":-53,"name":"...","serviceUuids":["6e400001b5a3f393e0a9e50e24dcca9e"]}
/ble/advertise/f5:47:c8:0b:49:04/rssi -53
/ble/advertise/f5:47:c8:0b:49:04/1809 [22]
/ble/advertise/f5:47:c8:0b:49:04/temp 22
/ble/temp/f5:47:c8:0b:49:04 22

You can now subscribe with MQTT to /hist/hour/ble/temp/f5:47:c8:0b:49:04 and every hour you will receive a packet containing the average temperature over that time.

However, you can also request historical data by sending the JSON:

{
  "topic" : "/hist/hour/ble/temp/f5:47:c8:0b:49:04",
  "interval" : "minute",
  "age" : 6
}

to /hist/request/a_unique_id. EspruinoHub will then send a packet to /hist/response/a_unique_id containing:

{
  "interval":"minute",
  "from":1531227216903, // unix timestamp (msecs since 1970)
  "to":1531234416903,   // unix timestamp (msecs since 1970)
  "topic":"/hist/hour/ble/temp/f5:47:c8:0b:49:04",
  "times":[ array of unix timestamps ],
  "data":[ array of average data values ]
}

Requests can be of the form:

{
  topic : "/ble/advertise/...",
  "interval" : "minute" / "tenminutes" / "hour" / "day"
  // Then time period is either:
  "age" : 1, // hours
  // or:
  "from" : "1 July 2018",
  "to" : "5 July 2018"     (or anything that works in new Date(...))
}

For a full example of usage see www/rssi.html.

HTTP Proxy

EspruinoHub implements the Bluetooth HTTP Proxy service

The HTTP Proxy is disabled by default as it can give any Bluetooth LE device in range access to your network. To fix this, edit the http_proxy and http_whitelist entries in config.json to enable the proxy and whitelist devices based on address (which you can find from EspruinoHub's status of MQTT advertising packets).

NOTE: Some Bluetooth adaptors (eg CSR / 0a12:0001) will cause the error Command Disallowed (0xc) when attempting to connect to a device when http_proxy is enabled.

To allow Bluetooth to advertise services (for the HTTP proxy) you also need:

# Stop the bluetooth service
sudo service bluetooth stop
# Start Bluetooth but without bluetoothd
sudo hciconfig hci0 up

See https://github.com/sandeepmistry/bleno

Home Assistant Integration

Follow the instructions at https://www.home-assistant.io/integrations/mqtt/ to enable Home Assistant to use an external MQTT broker. Assuming you're running on the same device as EspruinoHub, use localhost as the IP address for the MQTT server.

Ensure that homeassistant is set to true in EspruinoHub's config.json. It's currently the default.

Now, in the Home Assistant main page you should see new Sensors and Binary sensors which match any devices that EspruinoHub has found!

Troubleshooting

When using the HTTP Proxy I get BLOCKED returned in the HTTP body

Your BLE device isn't in the whitelist in config.json - because the HTTP Proxy exposes your internet connection to the world, only BLE devices with the addresses you have specified beforehand are allowed to connect.

TODO