Giter VIP home page Giter VIP logo

bleperipheral's Introduction

blePeripheral V2

This is a Node.js class for creating a Bluetooth LE (Low Energy) peripheral based on Bluez 5.50 and its D-Bus based API. This class supports secure encrypted connections to IOS introduced in Bluetooth 4.2 as LE Secure connections. If you want to securely connect your IOS device (Android should also work) to your Raspberry Pi with Bluetooth LE you must implement this level of security or IOS will not allow the device to pair and be bound.

Version 2 (June 1st, 2020)

The primary goal of version 2 is to migrate away from the dependency on dbus-native. For the most part, existing programs that depend on blePeripheral.js V1 will continue to function with little to no change in V2. See Version 2 details lower in this readme.

Hardware Requirements

This class was developed based on the Raspberry Pi Zero W’s built in bluetooth radio. Other Raspberry Pi models may work but only the Pi Zero W has been tested.
To properly test this class, you will need an IOS device with the BLE development tool LightBlue Explorer. I have tested it with an iPhone 7 and iPad pro. It should also work with an Android device I just haven't tested it. There is a LightBlue install on the Google Play store. If someone can give this a try and document the results I will be happy to include a link to their site.

Software Requirements

This class requires blueZ version 5.50 now included in latest version Raspbian Buster. If you are using Buster you no longer need to update bluetooth on the Pi.

Version 2 requires libdbus, glib2.0, and gdbus

  • libdbus

    $ sudo apt-get install libdbus-1-dev

  • glib2.0

    $ sudo apt-get install libglib2.0-dev

  • gdbus

    gdbus is part of the core Raspbian Buster install. You shouldn't need to install it.

Install and load sampleApp

on the Raspberry Pi Zero W from a SSH session:

  • Type git clone https://github.com/RuckerGauge/blePeripheral.git
  • Type cd blePeripheral && npm install
  • Type sudo cp ./examples/sampleApp.conf /etc/dbus-1/system.d This gives our sample app permission to bring up a service on the system D-Bus.
  • Type node ./examples/sampleApp

At this point you should have a Bluetooth LE (BLE) peripheral up and running on your Raspberry Pi Zero W. The sample app sets up several test characteristics you can connect to for testing. It also starts advertising as a BLE service so a bluetooth central (your iPhone) can find and connect to it. The next step is to connect to this peripheral from an IOS device.

Test with iPhone

  • Install the LightBlue Explorer app on your iPhone or iPad and open it.

  • The Peripherals Nearby list should have your device as you named it in the main.conf. If you followed my bluetooth 5.50 install steps the name will be rGauge Transmitter. Tap on that device to open and connect to the Raspberry Pi Zero W peripheral. You should see the following screens:

    pic1 pic2

  • You will see five characteristics labeled isAuthorized, cmd, bigData, myIpAddress and cpuTemp. The first two can be accessed without binding to the Raspberry Pi as they have normal Read and Write flags set. However, the last three (bigData, myIpAddress, cpuTemp) require that you pair with your iPhone before you can access their data. Their characteristics are flagged as encrypt-read and encrypt-write. So for now do not tap on them we will stay focused on the first two.

  • Tap on the isAuthorized characteristic to open and read its value. You will see a hex value that doesn’t make much sense. It is hex encoded ASCII characters and to decode it you can tap on the word hex in upper right side of the screen. Select the UTF-8 String from the list of options and you will see that the value is the word “false”. This characteristic can be used to tell your IOS app if this device is bound or not with this peripheral. It will change to “true” when we pair the iPhone to the device.

    pic3 pic4

  • A word about pairing with the iPhone. To trigger the pairing / bonding process your iPhone needs to try and read a secure characteristic. The iPhone will be denied access to the characteristic and sent an Insufficient Encryption error. The iPhone in turn will try to pair with the peripheral by sending its capabilities that layout the type of connection it will accept. This process is taken care of for us by the bluetooth daemon. Our app doesn’t have to do anything other than tell the bluetooth radio it is allowed to pair. By default, I have pairing disabled so if you tap on one of these secure characteristics you will not be prompted to pair because it is not allowed. It will act like it is reading the value but nothing is returned. More on that later… You can monitor the pairing process by opening another SSH connection to your Raspberry Pi Zero W and type suod btmon. This is one of the best tools for troubleshooting connection problems. When an iPhone pairs with your peripheral for the first time it exchanges several encryption keys, and stores them on the Raspberry Pi so it won’t have to do this again. This is called bonding (it is possible to pair with a device and not bond to it. However, the iPhone requires a device to be bound). Once you successfully pair with a peripheral your iPhone is also bound to it and ready to encrypt and de-encrypt data as it is transmitted and received over the bluetooth radio.

    pic5 pic6

  • To trigger the pairing process normally a user would push a button on their Raspberry Pi. But for testing purposes I have setup a special characteristic that you can use to simulate a button push. That is what the cmd characteristic does. To Open your peripheral for pairing select the cmd characteristic and convert the encoding from hex to UTF-8. When you do that you will see the value of the data read is a menu of options 1=enable pairing, 2=disable pairing. 3=log adapter, 4=log connected device. We want to enable pairing so tap on the “Write new value” text and type the number 1 on your iPhone’s keyboard and then done. Now you're peripheral is accepting pairing request and to start that process you need to access a secure characteristic. Back up one screen (swipe right) and tap on the bigData characteristic. A Bluetooth Pairing Request will pop up and you can hit Pair to complete the process. From now on your phone will be bound to your Raspberry Pi Zero W bluetooth radio. You will not have to go through this process again.

    pic7


That’s it, your bound to your iPhone and off and running. However, there is a bug and a work-around with the Raspberry Pi Zero. If you restart you'er raspberry Pi after bonding with an iPhone it will not be able to communicate with the phone once the phone’s bluetooth address changes. To work around this type sudo systemctl restart bluetooth after your Pi has booted back up. Then restart your node app and your iPhone will be able to connect as a bound device.

Version 2 details (published summer of 2020)

Since Dbus-native was not being maintained, and caused NPM warnings, I selected a new dbus library. I looked at several dbus library’s for node and Shougun’s node-dbus still seems to be one of the fastest and best maintained out there. However, I ran into some issues with his implementation and had to add some features to his encoder.cc source file. So for now this package references my fork of his code.

All classes have been rewritten to use the new dbus connection. I also rewrote the _connetionManager() method in the BlePeripheral.js class. It is faster and cleaner but requires the command line utility /usr/bin/gdbus be installed on the Raspberry Pi. This shouldn’t be a problem as gdbus is installed by default in buster.

See this class in action

The Raspberry Pi used in the GDT at https://WallGauge.com is based on this library. Scroll to the bottom of the page to see a video of how the pairing works with an iPhone.

Tools

Monitor dbus and bluetooth traffic

  • sudo btmon
  • sudo dbus-monitor --system

Command line tools to view and call dbus methods

  • gdbus introspect --system --dest com.sampleApp --object-path / --recurse
  • gdbus call --system --dest org.bluez --object-path /org/bluez/hci0 --method org.bluez.GattManager1.RegisterApplication "/com/sampleApp" "{'string':<''>}"
  • gdbus call --system --dest org.bluez --object-path /org/bluez/hci0 --method org.bluez.GattManager1.RegisterApplication "/com/sampleApp" "{}"
  • gdbus call --system --dest org.bluez --object-path /org/bluez/hci0 --method org.freedesktop.DBus.Properties.GetAll org.bluez.Adapter1
  • gdbus call --system --dest com.gdtMan --object-path /com/gdtMan/gaugeAlert --method org.freedesktop.DBus.Properties.GetAll org.bluez.GattCharacteristic1
  • gdbus call --system --dest com.gdtMan --object-path /com/gdtMan/gaugeAlert --method org.freedesktop.DBus.Properties.Get org.bluez.GattCharacteristic1 Value
  • More info on the GVarinet syntax for gdbus

Notes on advertisement interval

The Raspberry Pi Zero W running Buster sends out its BLE advertisement packet every 1.28 seconds. I have been trying to figure out how to bump that delay down to 200mS between packets. As of May 15, 2020 I haven't come up with a way to do it. The following are my notes and research on this.

There is a hcitool command that may work but I want to do it over the dbus interface:

Another option appears to be changing the value in the adv_min_interval and adv_max_interval files with the following two commands. But the change is lost when the Pi is rebooted.

  • To changed the min advertisement interval to 200ms echo 320 | sudo tee /sys/kernel/debug/bluetooth/hci0/adv_min_interval.
  • To changed the max advertisement interval to 300ms echo 480 | sudo tee /sys/kernel/debug/bluetooth/hci0/adv_max_interval.
  • The default is set to 2048. 2048 X 0.625ms = 1.28 seconds.

Came across this information for bluez Android. It looks like they have a method in the advertisement class that will allow us to do it. I hope this will be added to the standard version of bluez that runs on the Pi.

bleperipheral's People

Contributors

dependabot[bot] avatar johnrucker avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

bleperipheral's Issues

Problem resolving a previously bound device's random address to its public address after a reboot.

If you reboot the Raspberry Pi, and the random address of a bound device (iPad in this example) changes, that device can no longer read or write a secure characteristic.
Symptom 1) The buetoothd daemon has an error during boot, the easiest way to see it is to issue a sudo systemctl status bluetooth. There will be three errors just after the initialized line:

  • Failed to set mode: Rejected (0x0b)
  • Failed to set mode: Rejected (0x0b)
  • Failed to set privacy: Rejected (0x0b)

Symptom 2) When your iPhone connects the sudo btmon log doesn't show any errors. It just doesn't resolve the iPhone's random address back to public as it did before the reboot. Here is a screen shot of what that looks like:
image

Symptom 3) When you iPhone tries to access a secure characteristic it gets an Insufficient Encryption error and then makes a LE Long Term Key Request. BlueZ responds with LE Long Term Key Request Neg Reply as shown below:
image

Workaround

If you restart bluetootd after a reboot sudo systemctl restart bluetooth this all goes away. The address will be resolved correctly. In the following screen shot of the log from sudo btmon you can see my iPad's random address B4:F6:1C:53:EF:B3 gets correctly resolved to its public address of B4:F6:1C:53:EF:B3.
image

I don't think this problem has anything to do with my node app. It has something to do with BlueZ and the Raspberry Pi. Looking for pointer on where to focus my troubleshooting efforts!

Update node-gyp to a version that supports python3

I am trying to run npm install on this repo on Fedora 38 on a laptop, but I get this error:

npm ERR! gyp info using [email protected]
npm ERR! gyp info using [email protected] | linux | x64
npm ERR! gyp ERR! configure error 
npm ERR! gyp ERR! stack Error: Python executable "/usr/bin/python" is v3.11.6, which is not supported by gyp.

some dependencies need to be updated, but even after updating the dbus version I am still getting other errors.

sometimes _connectionManager will not recognize connection state change

file: blePeripheral.js

connectionManager checks if strData starts with **'/org/bluez/hci0/dev'**
in many cases, packets are linked together (i guess it depends on the processing power). when grouped together, this condition fails.

a simple solution, split strData by \n and scan all packets.

Build error on Buster, Pi4 w/ Bluez 5.52

Attempting to test your blePeripheral but I can't build the project. I followed your Bluez 5.50 instructions for installing node.

I'm running Bluez 5.52

Any guidance based on the following errors?

make: *** [abstract_socket.target.mk:109: Release/obj.target/abstract_socket/src/abstract_socket.o] Error 1
make: Leaving directory '/home/we/blePeripheral/node_modules/abstract-socket/build'
gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:194:23)
gyp ERR! stack     at ChildProcess.emit (events.js:311:20)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:275:12)
gyp ERR! System Linux 4.19.66-v7l+
gyp ERR! command "/usr/local/bin/node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /home/we/blePeripheral/node_modules/abstract-socket
gyp ERR! node -v v12.16.1
gyp ERR! node-gyp -v v5.0.5
gyp ERR! not ok 
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules/abstract-socket):
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] install: `node-gyp rebuild`
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: Exit status 1

up to date in 4.169s
fixed 0 of 1 vulnerability in 26 scanned packages
  1 vulnerability required manual review and could not be updated
10.0.1.21 ~/blePeripheral $ npm audit
                                                                                
                       === npm audit security report ===                        
                                                                                
┌──────────────────────────────────────────────────────────────────────────────┐
│                                Manual Review                                 │
│            Some vulnerabilities require your attention to resolve            │
│                                                                              │
│         Visit https://go.npm.me/audit-guide for additional guidance          │
└──────────────────────────────────────────────────────────────────────────────┘
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ Low           │ Sensitive Data Exposure                                      │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ put                                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ No patch available                                           │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ dbus-native                                                  │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ dbus-native > put                                            │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/1007                            │
└───────────────┴──────────────────────────────────────────────────────────────┘

versions

10.0.1.21 ~/blePeripheral $ node -v
v12.16.1
10.0.1.21 ~/blePeripheral $ npm -v
6.13.4

Linux Kernel 5.10.11 breaks Bluetooth LE Binding

As of Feb 23, 2021 the Linux kernel (5.10.11) for the Raspberry Pi is preventing this class from binding to an iPhone. The Linux kernel has disabled default-agent that is used by this class during the binding process.

You can work around this by using bluetoothctl.

$ bluetoothctl
[bluetooth] agent on
[bluetooth] default-agent
Default agent request successful

The problem with this workaround is bluetoothctl has the remain running during the binding process with the phone.

Here is the post on the linux GitHub where I reported the issue
Here is a post on the Raspberry Forum where I reported the issue

Advertising a secondary service

While i am trying to register a secondary service i am getting an error

Error calling RegisterAdvertisement method.
Error calling RegisterAdvertisement method. DBusError: Maximum advertisements reached
    at new DBusError (/home/pi/DIGH_LITT_Cerberus-cerberusAdvertising/node_modules/dbus/lib/error.js:9:9)
    at createError (/home/pi/DIGH_LITT_Cerberus-cerberusAdvertising/node_modules/dbus/lib/bus.js:243:9) {
  dbusName: 'org.bluez.Error.NotPermitted'
}
/home/pi/DIGH_LITT_Cerberus-cerberusAdvertising/node_modules/dbus/lib/service.js:15
		var iface = self.objects[objectPath]['interfaces'][interfaceName];
		                                    ^

TypeError: Cannot read property 'interfaces' of undefined
    at module.exports.<anonymous> (/home/pi/DIGH_LITT_Cerberus-cerberusAdvertising/node_modules/dbus/lib/service.js:15:39)
    at module.exports.emit (events.js:315:20)
    at /home/pi/DIGH_LITT_Cerberus-cerberusAdvertising/node_modules/dbus/lib/dbus.js:25:16

Any idea what is happening here ?

Notifying not being read correctly by BlueZ

Notification request not received after a user disconnects. If a ble client connects and request notifications for a characteristic (that supports them) everything works fine the first connection. However, if the user disconnects without stopping the notification request and reconnects the new session will not receive the notifications. If the client request to listen to notifications bluez will not call startNotify.

Workarounds: This is only an issue if your app stops notifying when the client disconnects. The easiest workaround is to just keep sending them if the flag is set and someone is connected.

Another workaround is to call the unRegisterGattService() and then registerGattService() methods after a client disconnects.

On a possibly related note: When blePeripheral receives a call to one of the characteristic's startNotify() or stopNotif() methods there is a D-Bus.Error.AccessDenied on the reply packet. This is strange as the startNotify() method is not returning anything. It appears to be an acknolgement packet. What is even stranger is everything works as expected.

method call time=1547649108.827050 sender=:1.5 -> destination=:1.32 serial=1074 path=/com/netConfig/cpuTemp; interface=org.bluez.GattCharacteristic1; member=StartNotify method return time=1547649108.854649 sender=:1.32 -> destination=:1.5 serial=57 reply_serial=1074

error time=1547649108.857959 sender=org.freedesktop.DBus -> destination=:1.32 error_name=org.freedesktop.DBus.Error.AccessDenied reply_serial=57 string "Rejected send message, 3 matched rules; type="method_return", sender=":1.32" (uid=0 pid=31089 comm="node sampleApp ") interface="(unset)" member="(unset)" error name="(unset)" requested_reply="0" destination=":1.5" (uid=0 pid=453 comm="/usr/libexec/bluetooth/bluetoothd --noplugin=wiimo")"

Cannot pair to access last 3 characteristics

RPI: Raspberry pi 3
Phone: Android

I can read and write isAuthorized and cmd characteristics, but when i try to pair to see last 3 i can't pair.

Idk why it doesn't work, here is a screen recording of what's happening.

screen-20221029-153740.mp4

Setting MFG data in for advertising

I'm unable to set the manufacturer id of the manufacturer data in advertisingClass.js file.

I added:
this._iface1.addProperty('ManufacturerData', { type: { type: '{q(y)}' }, getter: (callback) => { callback(null, { 0xffff : [0x01, 0x02, 0x03, 0x04, 0x05] } ); } });

using nRFConnect, I can see the advertising as should, but the manufacturer id is incorrect.
I get a different id every time I run the app.

I also tried to play with the type with many other combinations ({qay} {say}, {sav} etc.)
I'm using BlueZ 5.50 on Raspberry PI Zero W

Emit events for notify and stop notify for characteristic

Instead of checking

if(characteristic.iface.Notifying) {
 // Do something
}

it would better to do something like

characteristic.on('notify', () => {
  // Watch a file or subscribe to something internally
})
characteristic.on('stopNotify', () => {
  // Stop watching file / unsubscribe to something internally
})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.