Experiments with Bluetooth (exploration prior to documentation)
Closed this issue · 3 comments
IOTstack – Experiments with Bluetooth
This issue documents my approach to getting node-red-contrib-generic-ble
working under IOTstack. I'm hoping that people with a lot more experience than me will spot any flaws in my approach.
I'm not going to keep typing
node-red-contrib-generic-ble
because it's boring. I'm going to use the abbreviation BLE.
I don't have a use-case for Bluetooth so I can't go any further than I've documented here. It would be very helpful to get confirmation that the result is fit for purpose and that useful communications can be achieved between Node-Red and Bluetooth devices.
If there's enough interest, I will volunteer to turn this into a documentation page for the IOTstack project. If someone else wants to tackle the documentation using this issue as a starting point, please go right ahead.
Installation
Give the current user access to the Bluetooth hardware
$ sudo usermod -G bluetooth -a $(whoami)
$ sudo reboot
Update to the latest and greatest of everything
Make sure Raspbian (ahem, "Raspberry Pi OS") is up-to-date:
$ cd ~/IOTstack
$ docker-compose down
$ cd
$ sudo apt update
$ sudo apt upgrade
$ sudo reboot
Make sure IOTstack is up-to-date:
$ cd ~/IOTstack
$ git pull origin master
Make sure container images are up-to-date:
$ docker rmi "iotstack_nodered" "nodered/node-red"
$ docker-compose pull
$ docker-compose up --build -d
Demonstrate that nodered is in non-host mode (the default)
In the following, 192.168.132.1 is my local router.
$ docker exec nodered traceroute 192.168.132.1
traceroute to 192.168.132.1 (192.168.132.1), 30 hops max, 38 byte packets
1 172.18.0.1 (172.18.0.1) 0.011 ms 0.015 ms 0.012 ms
2 myrouter.mydomain.com (192.168.132.1) 0.288 ms 0.263 ms 0.143 ms
$
Two hops = non-host mode.
Edit the nodered configuration
Edit ~/IOTstack/docker-compose.yml and add these lines:
- /var/run/docker.sock:/var/run/docker.sock
- /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
devices:
- "/dev/ttyAMA0:/dev/ttyAMA0"
- "/dev/vcio:/dev/vcio"
- "/dev/gpiomem:/dev/gpiomem"
network_mode: "host"
The final result should be:
nodered:
container_name: nodered
build: ./services/nodered/.
restart: unless-stopped
user: "0"
privileged: true
env_file: ./services/nodered/nodered.env
ports:
- 1880:1880
volumes:
- ./volumes/nodered/data:/data
- /var/run/docker.sock:/var/run/docker.sock
- /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
devices:
- "/dev/ttyAMA0:/dev/ttyAMA0"
- "/dev/vcio:/dev/vcio"
- "/dev/gpiomem:/dev/gpiomem"
network_mode: "host"
Remember, you can't use tabs in docker-compose.yml. You must use spaces.
Apply the changes:
$ docker-compose up -d
…
Recreating nodered ... done
$
Confirm that nodered is in host mode
$ docker exec nodered traceroute 192.168.132.1
traceroute to 192.168.132.1 (192.168.132.1), 30 hops max, 38 byte packets
1 myrouter.mydomain.com (192.168.132.1) 0.252 ms 1.610 ms 0.200 ms
$
One hop = host mode.
Confirm that Node-Red (the service) is reachable
You can either open a browser and point it to port 1880 on your Raspberry Pi's IP address or domain name, or you can do it from the command line:
$ curl -I localhost:1880
HTTP/1.1 200 OK
…
$
As long as you get the "200 OK" you should be fine.
Install node-red-contrib-generic-ble
Open a shell into the nodered container:
$ docker exec -it nodered bash
#
Confirm that BLE is not already installed:
# ls -ld node_modules/node-red-contrib-generic-ble
ls: node_modules/node-red-contrib-generic-ble: No such file or directory
#
BLE has a dependency that the package does not take care of:
# apk add eudev-dev
fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/main/armv7/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/community/armv7/APKINDEX.tar.gz
(1/2) Installing pkgconf (1.6.3-r0)
(2/2) Installing eudev-dev (3.2.9-r1)
Executing busybox-1.31.1-r9.trigger
OK: 240 MiB in 67 packages
#
Install BLE:
# npm install node-red-contrib-generic-ble
The compile chucks out a lot of notes and warnings but succeeds.
Confirm that BLE has been installed:
# ls -ld node_modules/node-red-contrib-generic-ble
drwxr-xr-x 5 root root 4096 Jun 3 21:00 node_modules/node-red-contrib-generic-ble
#
Exit the container shell:
# exit
$
Restart nodered. This picks up the new BLE node:
$ docker-compose restart nodered
Check for errors:
$ docker logs nodered
The output I get is:
> node-red-docker@1.0.6 start /usr/src/node-red
> node $NODE_OPTIONS node_modules/node-red/red.js $FLOWS "--userDir" "/data"
3 Jun 21:05:05 - [info]
Welcome to Node-RED
===================
3 Jun 21:05:05 - [info] Node-RED version: v1.0.6
3 Jun 21:05:05 - [info] Node.js version: v10.20.1
3 Jun 21:05:05 - [info] Linux 4.19.118-v7l+ arm LE
3 Jun 21:05:06 - [info] Loading palette nodes
3 Jun 21:05:08 - [info] Dashboard version 2.22.1 started at /ui
3 Jun 21:05:09 - [info] Settings file : /data/settings.js
3 Jun 21:05:09 - [info] Context store : 'default' [module=memory]
3 Jun 21:05:09 - [info] User directory : /data
3 Jun 21:05:09 - [warn] Projects disabled : editorTheme.projects.enabled=false
3 Jun 21:05:09 - [info] Flows file : /data/flows.json
3 Jun 21:05:09 - [info] Server now running at http://127.0.0.1:1880/
3 Jun 21:05:09 - [warn]
---------------------------------------------------------------------
Your flow credentials file is encrypted using a system-generated key.
If the system-generated key is lost for any reason, your credentials
file will not be recoverable, you will have to delete it and re-enter
your credentials.
You should set your own key using the 'credentialSecret' option in
your settings file. Node-RED will then re-encrypt your credentials
file using your chosen key the next time you deploy a change.
---------------------------------------------------------------------
3 Jun 21:05:09 - [info] Starting flows
3 Jun 21:05:09 - [info] Started flows
3 Jun 21:05:09 - [info] [sqlitedb:eb5d5c76.848308] opened /data/Databases/Mailbox/mailbox.db ok
3 Jun 21:05:09 - [info] [sqlitedb:f51495af.860dd8] opened /data/Databases/TrigBoard/trigboard.db ok
3 Jun 21:05:09 - [info] [mqtt-broker:Merle MQTT] Connected to broker: mqtt://mosquitto:1883
3 Jun 21:05:09 - [info] [mqtt-broker:Merle MQTT] Connected to broker: mqtt://mosquitto:1883
3 Jun 21:05:09 - [info] [mqtt-broker:Merle MQTT] Connected to broker: mqtt://mosquitto:1883
3 Jun 21:05:09 - [info] [mqtt-broker:Merle MQTT] Connected to broker: mqtt://mosquitto:1883
3 Jun 21:05:09 - [info] [mqtt-broker:Docker MQTT] Connected to broker: mqtt://mosquitto:1883
$
No errors!
Testing
- Use your browser to connect to Node-Red.
- Created a new empty flow.
- Scroll down in the Node Palette until you see "Generic BLE in" and drag it onto the canvas.
- Double-click the BLE-in node to open its properties.
- Click the pencil icon next to "Add new Generic BLE...".
- Turn on the "BLE Scanning" checkbox.
- Wait. If all goes well, the "Scan Result" popup menu will populate and you will be able to open that popup menu and see all your Bluetooth MACs.
Discussion
First attempt at getting BLE working (failure):
-
Starting docker-compose.yml configuration:
nodered: container_name: nodered build: ./services/nodered/. restart: unless-stopped user: "0" privileged: true env_file: ./services/nodered/nodered.env ports: - 1880:1880 volumes: - ./volumes/nodered/data:/data
-
I forced a complete rebuild of
iotstack_nodered
. The nodes installed at build time were the defaults plus SQLite. -
I edited docker-compose.yml, to add:
network_mode: "host"
Then restarted nodered and checked that it was in host mode.
-
At HilscherAutomation I came across the requirement for:
devices: - "/dev/ttyAMA0:/dev/ttyAMA0" - "/dev/vcio:/dev/vcio" - "/dev/gpiomem:/dev/gpiomem"
There were more lines in the HilscherAutomation list but the above three were the only ones that mapped to anything on my RPi4. They seemed like a good idea but I do not know whether they are required.
-
My first attempt at compiling BLE failed because of a missing dependency. I worked out how to install
eudev-dev
, after which BLE compiled. -
Restarting the nodered container to activate BLE produced an error about
/var/run/docker.sock
so I added a mapping:volumes: - ./volumes/nodered/data:/data - /var/run/docker.sock:/var/run/docker.sock
-
That cured the
docker.sock
problem but nodered would still not come up. The new error was:Error: connect ENOENT /var/run/dbus/system_bus_socket at PipeConnectWrap.afterConnect [as oncomplete] (net.js:1107:14)
-
I tried to solve that by adding another mapping:
volumes: - ./volumes/nodered/data:/data - /var/run/docker.sock:/var/run/docker.sock - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
but nodered kept restarting with the same error.
I checked a few more things (eg possible permission conflicts) but that's where I gave up.
Second attempt at getting BLE working (success):
-
Starting docker-compose.yml configuration:
nodered: container_name: nodered build: ./services/nodered/. restart: unless-stopped user: "0" privileged: true env_file: ./services/nodered/nodered.env ports: - 1880:1880 volumes: - ./volumes/nodered/data:/data - /var/run/docker.sock:/var/run/docker.sock - /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket devices: - "/dev/ttyAMA0:/dev/ttyAMA0" - "/dev/vcio:/dev/vcio" - "/dev/gpiomem:/dev/gpiomem"
-
I forced a complete rebuild of
iotstack_nodered
. The nodes installed at build time were the defaults plus SQLite. -
I edited docker-compose.yml, to add:
network_mode: "host"
Then restarted nodered and checked that it was in host mode.
-
I installed the
eudev-dev
dependency and compiled BLE. -
I restart the nodered container.
And it just worked.
To say that I was astonished would be a severe understatement.
My guess (and it is no more than a guess) is that some or all of the volumes
and/or devices
additions to docker-compose.yml needed to be in place before BLE was compiled.
More work
I tried to see if I could add node-red-contrib-generic-ble
to the menu but that, of course, came with the eudev-dev
pre-requisite. I tried adding:
RUN apk add eudev-dev
as the second line of the Dockerfile but docker-compose
hated it.
I will leave it to others to solve this problem.
Just be aware that, in the meantime, a rebuild of nodered will require a manual install of BLE.
I solved the problem described under "More work". This is the Dockerfile I used:
FROM nodered/node-red:latest
USER root
RUN apk update
RUN apk upgrade
RUN apk add --no-cache eudev-dev
RUN for addonnodes in \
node-red-node-pi-gpiod \
node-red-dashboard \
node-red-contrib-influxdb \
node-red-contrib-generic-ble \
; do \
npm install ${addonnodes} ;\
done;
RUN npm install --unsafe-perm node-red-node-sqlite
Build process:
$ cd ~/IOTstack
$ docker-compose down
$ docker rmi "iotstack_nodered"
$ docker-compose up --build -d
Notes:
-
The permissions problem that I hit before was solved by adding
USER root
. -
The base
nodered/node-red
image is built on Alpine Linux, which is whyapk
is needed instead ofapt
. -
These three lines could probably be hard-coded:
USER root RUN apk update RUN apk upgrade
-
The
RUN apk add --no-cache eudev-dev
could also be hard-coded. It's only providing a library. Other than occupying some compile time, it would do no harm even if BLE was not being installed. The alternative would be to adopt the pattern for SQLite and use a flag to turn installation on when appropriate. -
The
--no-cache
flag seems to appear in most examples of usingapk
to install packages via Dockerfiles. I tried building both with and without that option. It worked either way so I can't say whether it is required. -
node-red-contrib-generic-ble
works OK as an argument to the for-loop (ie it doesn't need special handling likenode-red-node-sqlite
).
Revised Magic Incantation
More research has led me to conclude that "host mode" is not required. The Docker documentation says:
Network | Description |
---|---|
host | Use the host's network stack inside the container |
In my testing, I can see nearby Bluetooth MACs via a "Generic BLE in" node irrespective of whether "host" mode is engaged or not.
Permissions
$ sudo usermod -G bluetooth -a $(whoami)
$ sudo reboot
docker-compose.yml
nodered:
container_name: nodered
build: ./services/nodered/.
restart: unless-stopped
user: "0"
privileged: true
env_file: ./services/nodered/nodered.env
ports:
- 1880:1880
volumes:
- ./volumes/nodered/data:/data
- /var/run/docker.sock:/var/run/docker.sock
- /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket
devices:
- "/dev/ttyAMA0:/dev/ttyAMA0"
- "/dev/vcio:/dev/vcio"
- "/dev/gpiomem:/dev/gpiomem"
The change from the default is to add the last six lines.
Dockerfile
FROM nodered/node-red:latest
USER root
RUN apk update
RUN apk upgrade
RUN apk add --no-cache eudev-dev
RUN for addonnodes in \
node-red-node-pi-gpiod \
node-red-dashboard \
node-red-contrib-influxdb \
node-red-contrib-generic-ble \
; do \
npm install ${addonnodes} ;\
done;
RUN npm install --unsafe-perm node-red-node-sqlite
Build
$ cd ~/IOTstack
$ docker-compose down
$ docker rmi "iotstack_nodered"
$ docker-compose up --build -d
Test
- Use your browser to connect to Node-Red.
- Created a new empty flow.
- Scroll down in the Node Palette until you see "Generic BLE in" and drag it onto the canvas.
- Double-click the BLE-in node to open its properties.
- Click the pencil icon next to "Add new Generic BLE...".
- Turn on the "BLE Scanning" checkbox.
- Wait.
If all goes well, the "Scan Result" popup menu will populate and you will be able to open that popup menu and see all your Bluetooth MACs.
Prepared pull request #70