Coding BLE on Raspberry Pi
Using GattLib to read values from a Victron SmartShunt over BlueTooth
One of my home projects involves keeping my Internet running during power fails. I have been using the SmartShunt from victronenergy to directly monitor the battery that feeds the UPS for my routers and satellite reciever.
The SmartShunt is an amazing device that keeps track of power flowing in an out of your battery and is able to track and communicate that data over a serial connection as well as bluetooth (BLE).
In most applications, people use the VictronConnect App to setup and monitor the SmartShunt over Bluetooth. However in my well pump project I connected it to a Raspberry Pi through the USB interface. The Victron USB interface cable enabled me to periodically read a key-value set of parameters with battery status.
For this project though, I am using a Raspberry Pi Zero W, and I didn’t want to waste the USB port. Rather, I would attempt to use the built-in Bluetooth Low Energy (BLE) modem.
After doing some research, I came across a post that announced the availability of the SmartShunt BLE service. This post mentioned that Victron product data such as Voltages, Currents, Power, etc would be available as GATT characteristics over BLE. GATT is short for Generic Attributes and is the preferred way to read and write over BLE.
Singing the Bluez
BlueZ is the name of the protocol stack that communicate over Bluetooth on Linux and thus Raspberry Pi. You can talk to Bluez using bluetoothctl
through the command line interface.
You can turn on and start Bluetooth
sudo systemctl enable bluetooth
sudo systemctl start bluetooth
For example if you wish to scan for devices:
bluetoothctl
[bluetooth]# scan on
[NEW] Device CA:9C:B9:8D:E3:70 Network UPS
In my case I already knew the name of my SmartShunt as “Network UPS” and by examining the scan I was able to get the Device address CA:9C:B9:8D:E3:70.
Then in bluetoothctl I was able to connect and paired my device with the default key of 00000.
[bluetooth]# connect CA:9C:B9:8D:E3:70
Attempting to connect to CA:9C:B9:8D:E3:70
[CHG] Device CA:9C:B9:8D:E3:70 Connected: yes
Connection successful
[Network UPS ]# pair CA:9C:B9:8D:E3:70
Attempting to pair with CA:9C:B9:8D:E3:70
Request passkey
[agent] Enter passkey (number in 0-999999): 000000
[CHG] Device CA:9C:B9:8D:E3:70 Paired: yes
Pairing successful
At this point the Raspberry Pi knows about the device and you can query it using gatttool
.
sudo gatttool -b CA:9C:B9:8D:E3:70 -t random -I
[CA:9C:B9:8D:E3:70][LE]> connect
Attempting to connect to CA:9C:B9:8D:E3:70
Connection successful
[CA:9C:B9:8D:E3:70][LE]> characteristics
.. a whole lot of data ...
The GATT characteristics are accessed by their UUIDs. Some of the useful UUIDs for the SmartShunt include.
State of Charge: 65970fff-4bda-4c1e-af4b-551c4cf74769
Battery Voltage: 6597ed8d-4bda-4c1e-af4b-551c4cf74769
Battery Current: 6597ed8c-4bda-4c1e-af4b-551c4cf74769
Power: 6597ed8e-4bda-4c1e-af4b-551c4cf74769
Ah Consumed: 6597eeff-4bda-4c1e-af4b-551c4cf74769
Battery Temp 6597edec-4bda-4c1e-af4b-551c4cf74769
Estimated Time: 65970ffe-4bda-4c1e-af4b-551c4cf74769
For example to read the state of charge we can do:
gatttool -t random -b CA:9C:B9:8D:E3:70 --char-read --uuid 65970fff-4bda-4c1e-af4b-551c4cf74769
handle: 0x002e value: 10 27
The reply 10 27 represents a 16-bit integer in hex in Little Endian byte order. So reversing that we find 0x2710 which is 10000 in decimal.. Since this value is in percent, we divide by 100 to get 100.00%
Let’s try getting the voltage:
gatttool -t random -b CA:9C:B9:8D:E3:70 --char-read --uuid 6597ed8d-4bda-4c1e-af4b-551c4cf74769
handle: 0x0032 value: 4e 05
In this case the value 4e 05 is 0x054e which is 1358 in decimal.. Since this value is in voltage, we divide by 100 to get 13.58 Volts.
I found that the best way to learn what was going on was play with the Nordic nRF Connect App as well as LightBlue on the iPhone.
Talking to Gatt from code
There is a GATT Protocol Python library that wraps around the implementation used by gatttool in bluez package. Python seems to be the preferred language to do BLE as most of the Bluez examples are written in it.
However I still prefer to code in C/C++ (OK boomer) and would rather use a C API if possible. I was able to find GattLib. The engineer did an amazing amount of work here and seemed to share the same frustration about a C API.
I had to do a slight bit of modification to his instructions to put the proper version on Debian Bullseye used on the Raspberry Pi. But assuming that you previously setup your Pi with git-core, and cmake then the easiest way I found was:
git clone https://github.com/labapart/gattlib.git
cd gattlib
mkdir build && cd build
cmake .. -DGATTLIB_BUILD_DOCS=OFF
# you will now find the lib in dbus/libgattlib.so
# I copied it to /usr/lib/libgattlib.so
You can then download the sample GIST I wrote and update the line that defines the device ID so it matches your SmartShunt:
const char* deviceID = "CA:9C:B9:8D:E3:70";
and compile and run the sample with clang (or I suppose gcc)
clang++ -Wall -o testshunt testshunt.c /usr/lib/libgattlib.so
./testshunt
Connected
SOC : 86.20%
Voltage : 13.23V
Current : 2.24A
Power : 30.00W
Consumed : -13.00Ah
Time 2 Go : 5 Hours 51 Mins
Bat Temp : 66.20°F
Disconnected
The important API calls to look at in the example were.
gattlib_connect_async()
gattlib_disconnect()
gattlib_string_to_uuid()
gattlib_read_char_by_uuid()
As an example, to read and print the battery state of charge.
const char* uuid_str_SOC
= "65970fff-4bda-4c1e-af4b-551c4cf74769";
uuid_t uuid;
int err = GATTLIB_SUCCESS;
// get SOC
if ( gattlib_string_to_uuid(uuid_str_SOC,
strlen(uuid_str_SOC) + 1, &uuid) == 0){
size_t len;
uint8_t *buffer = NULL;
err = gattlib_read_char_by_uuid(connection,
&uuid, (void **)&buffer, &len);
if(err == GATTLIB_SUCCESS){
int16_t i = (buffer[1] << 8) | buffer[0];
double val = (double) (i) / 100.0;
printf("\t%-10s: %0.2f%%\n", "SOC", val);
free(buffer);
}
}
Remember to pair first
I wasn’t able to code the Bluetooth pairing and device connection yet. So for now, you will need to follow my steps above to see bluetoothctl to pair with.
Happy hacking.
I noticed issues recently also, I am in the middle of moving my laptop development systems from MacOS to linux at the moment and can't get to debugging right now.. I would suggest asking the folks at Victron what they changed. -- let me know if you find anything out useful
Thanks for your great work!
What is the firmware version of your device. I already fail here:
[bluetooth]# connect E0:E2:D5:3C:XX:XX
Attempting to connect to E0:E2:D5:3C:XX:XX
[CHG] Device E0:E2:D5:3C:XX:XX Connected: yes
Failed to connect: org.bluez.Error.Failed
[CHG] Device E0:E2:D5:3C:XX:XX Connected: no
Is it about encryption maybe? Where to debug?
Thank you