node-ble-host
node-ble-host copied to clipboard
GATT cache behavior
Forgive the ignorance. I have done some research and haven't been able to find much. Is it possible to hint to the centrals to not cache the gatt data? I significantly change the info based on a number of criteria and run into issues with caching.
On my linux machine it's easy to disable cache functionality but for others I need a bullet proof approach to ensure the centrals don't fail due to caching (could be any os and/or android/ios).
Any tips?
Thanks!
Du you mean caching the structure of the gatt services, characteristics and descriptors?
Yeah. I switch the service/characteristics based on certain state in the device. I have clients (centrals) which I have 0 control over and if they cache the data and I have no way to 'fix' that then I will need to come up with another design.
Well, according to the BLE spec, the only cases the GATT client is allowed to cache the structure is when any of the following is true:
- The devices are bonded.
- The Service Changed Characteristic is not present in the GATT server.
This library exposes the Service Changed Characteristic so only option 1 should be relevant. So, simply make sure to not bond the devices to not make the client cache the GATT structure between connections.
If you want the client to be informed of the changes you have made, you should send a Service Changed Indication on the Service Changed Characteristic, with the affected handle range as value. This is the "correct way" to inform a GATT client that the services have changed. This can be sent during an on-going connection if the services are modified while connected and should also be sent to bonded devices immediately after connection setup, in case the service declarations have changed while the device was disconnected.
Alternatively, you can use a different identity when you use different set of services in your GATT database by changing the Bluetooth Device address of the GATT server. In particular, use a Static Random address when initializing the BleManager as explained in this section: https://github.com/Emill/node-ble-host/blob/master/docs/api/ble-manager.md#creating-a-blemanager-object.
OK great! I don't really change the values on the fly..I wait for the current client to disconnect and re-access the situation before advertising again. Do you happen to have any specific example of leveraging the service changed characteristic? I assume I essentially just need to declare it and that's it (central will no longer cache).
Well, I am simply doing BLE so I assume no bonding is involved (I don't exchange pins or anything of that nature).
Just Works bonding is allowed by default. To disallow that, use the code in this section: https://github.com/Emill/node-ble-host?tab=readme-ov-file#example---pairing-should-be-disabled. If the client still caches the GATT db between connections, it is not following the spec.
To send indications for the Service Changed characteristic, you can use this method https://github.com/Emill/node-ble-host/blob/master/docs/api/gatt-server.md#gettdbgetsvcccharacteristic to get access to it, then call the notifyAll method on it with Buffer.from([0, 0, 0xff, 0xff]) if you simply want to indicate that the full range has been changed to all connected devices.
I don't have any pairing handlers so I doubt that's in play but I will put that code in place just to be sure. When I connect with LightBlue I do not see the characteristic however. Are you suggesting I should see that no matter what?
That characteristic is automatically added when the library is initialized: https://github.com/Emill/node-ble-host/blob/master/lib/internal/gatt.js#L846. Don't you see that one?
It's not there in Light Blue no.
function startAdv() {
manager.gattDb.removeService(services[SERVICE_UUID]);
manager.gattDb.removeService(services[JOIN_SERVICE_UUID]);
if (ConfigManager.SIDEKICK_DEVICE_TOKEN) {
manager.gattDb.addServices([services[SERVICE_UUID]]);
manager.setAdvertisingData(serviceAdvDataBuffer);
advertised_service_uuid = SERVICE_UUID;
} else {
manager.gattDb.addServices([services[JOIN_SERVICE_UUID]]);
manager.setAdvertisingData(joinServiceAdvDataBuffer);
advertised_service_uuid = JOIN_SERVICE_UUID;
}
manager.startAdvertising(
{
/*options*/
},
connectCallback
);
console.log(
`${listener.constructor.name} server advertising: dev=${activeDevice.name}, mac=${activeDevice.bdaddr}, service_uuid=${advertised_service_uuid} (${advertised_service_uuid == SERVICE_UUID ? 'normal' : 'join'}), name=${deviceName}, localName=${deviceLocalName}`
);
}
Do you use LightBlue on iOS? I don't think iOS exposes that service to apps. If you want to confirm that the characteristic is there you can try on Android.
I am on ios, let me try it on android real quick...
Indeed the characteristic is there. Let me do some digging on other OSes and flesh out the behavior a little better and report back my findings. I can say that for sure on linux the bluez stack is caching unless explicitly turned off globally...which is unfortunate. I will confirm bonding isn't happening. I am only doing LE stuff and all of it is through web bluetooth (chromium) and ionic.
This seems to work as explained. Thanks for the assistance!