{"id":15916950,"url":"https://github.com/sorrir/node-bluetooth","last_synced_at":"2025-08-22T11:32:55.128Z","repository":{"id":57161593,"uuid":"281366287","full_name":"sorrir/node-bluetooth","owner":"sorrir","description":"A BLE library for both central modules and peripherals - built upon bluez, the official Linux Bluetooth protocol stack.","archived":false,"fork":false,"pushed_at":"2023-01-31T18:03:19.000Z","size":731,"stargazers_count":5,"open_issues_count":6,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-01T04:12:53.878Z","etag":null,"topics":["ble","bluetooth","bluetooth-low-energy","bluez","dbus","node"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@sorrir/bluetooth","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sorrir.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-21T10:25:15.000Z","updated_at":"2023-07-01T12:46:50.000Z","dependencies_parsed_at":"2023-02-16T22:00:54.414Z","dependency_job_id":null,"html_url":"https://github.com/sorrir/node-bluetooth","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/sorrir/node-bluetooth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorrir%2Fnode-bluetooth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorrir%2Fnode-bluetooth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorrir%2Fnode-bluetooth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorrir%2Fnode-bluetooth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sorrir","download_url":"https://codeload.github.com/sorrir/node-bluetooth/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sorrir%2Fnode-bluetooth/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263597089,"owners_count":23486293,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ble","bluetooth","bluetooth-low-energy","bluez","dbus","node"],"created_at":"2024-10-06T18:07:18.886Z","updated_at":"2025-07-11T08:10:47.705Z","avatar_url":"https://github.com/sorrir.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## `@sorrir/bluetooth` is no longer maintained. Development continues as `blauzahn` (see [github](https://github.com/QuadratClown/blauzahn) and [npm](https://www.npmjs.com/package/blauzahn))\n\n# @sorrir/bluetooth\n\n`@sorrir/bluetooth` is a BLE library built upon `bluez`, the official Linux Bluetooth protocol stack. It offers several layers of abstraction, allowing both implementing a central module as well as a custom peripheral.\n\nIn its core, `@sorrir/bluetooth` is a full wrapper around `bluez` and tries to closely resemble the original structure. On the lowest level, `bluez` interfaces can be interacted with directly. This for instance allows a straightforward translation of code snippets or [examples from the bluez repository](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/test) that were originally written in other languages. It additionally packs a ready-made implementation of simple, text based device communication via Bluetooth's Generic Attribute Profile (GATT).\n\n`@sorrir/bluetooth` has full TypeScript support. All necessary types come bundled with the package.\n\n## Prerequisites\n\n### Setup\n\nFirst, make sure which version of `bluez` you have installed:\n```console\nbluetoothd -v\n```\n`@sorrir/bluetooth` has been tested with `bluez 5.50` or newer. It might work on older versions as well, but if you run into problems, make sure to update `bluez` first.\n\nAfterwards you can install the package from npm\n```console\nnpm install @sorrir/bluetooth\n```\nThe next step is optional, however it is **strongly** recommended. By default, `@sorrir/bluetooth` can only communicate with `bluez` as a root user. To avoid this, create the file `/etc/dbus-1/system.d/sorrir-bluetooth.conf` with the following content:\n```xml\n\u003c!-- This configuration file specifies the required security policies\n     for the @sorrir/bluetooth npm package to work. --\u003e\n\n\u003c!DOCTYPE busconfig PUBLIC \"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\"\n \"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\"\u003e\n\u003cbusconfig\u003e\n  \u003cpolicy user=\"\u003cYOUR_USER\u003e\"\u003e\n    \u003callow own=\"org.bluez\"/\u003e\n    \u003callow send_destination=\"org.bluez\"/\u003e\n    \u003callow send_interface=\"org.bluez.GattCharacteristic1\"/\u003e\n    \u003callow send_interface=\"org.bluez.GattDescriptor1\"/\u003e\n    \u003callow send_interface=\"org.bluez.LEAdvertisement1\"/\u003e\n    \u003callow send_interface=\"org.freedesktop.DBus.ObjectManager\"/\u003e\n    \u003callow send_interface=\"org.freedesktop.DBus.Properties\"/\u003e\n  \u003c/policy\u003e\n\u003c/busconfig\u003e\n```\nMake sure to replace `\u003cYOUR_USER\u003e` with your user name. Note that the above configuration is only for core BLE functionality using the Generic Attribute Profile (GATT). If you intend to use more advanced functionality like custom agents, some additional send interfaces might have to be added. If you start receiving `org.freedesktop.DBus.Error.AccessDenied` errors, add the required interface(s) from the following list:\n```xml\n\u003callow send_interface=\"org.bluez.Agent1\"/\u003e\n\u003callow send_interface=\"org.bluez.Profile1\"/\u003e\n\u003callow send_interface=\"org.bluez.MediaEndpoint1\"/\u003e\n\u003callow send_interface=\"org.bluez.MediaPlayer1\"/\u003e\n\u003callow send_interface=\"org.mpris.MediaPlayer2.Player\"/\u003e\n```\n\n### Compatibility\n\n`@sorrir/bluetooth` itself is written in pure TypeScript that was transpiled to `ES5` and should therefore not cause any compatibility issues. However it uses `dbus-next` to communicate with `bluez`, which might limit the compatibility to certain architectures or node versions. For more info, visit the [dbus-next npm package](https://www.npmjs.com/package/dbus-next).\n\n### TypeScript\n\nWhile it is possible to use the package with plain JavaScript, it is recommended to use TypeScript for better type safety. All required types of the package come bundled with it.\n\n## Overview\n\n### Package structure\n\n`@sorrir/bluetooth` in its current state contains two main parts: `core` and `uart`, offering different levels of abstraction. Both are included if you import the package as a whole, for example with\n```ts\nimport * as sb from '@sorrir/bluetooth'\n```\nIf you want to import the parts separately, you can do so for example with\n```ts\nimport * as sbCore from '@sorrir/bluetooth/lib/core/index'\nimport * as sbUart from '@sorrir/bluetooth/lib/uart/index'\n```\nGenerally, every subfolder that is intended to be imported has an `index.js` file, which can be used to split imports into separate statements if desired.\n\n\n### Core\n\n`core` is for the most part a wrapper around the `bluez` D-Bus API. It allows either using interfaces as a client (to interfaces that are implemented as part of `bluez`) or providing interfaces as host (custom interfaces that are implemented by the user).\n\n### Uart\n\n`uart` is a layer of abstraction above the `core` components that allows simple communication of two ore more bluetooth devices via GATT. One device acts as server and the other devices as clients. After the connections are established, all devices can send or receive messages. The established channel is a bus, so every sent message is received by every connected device.\n\n## Get Started\n\n### Simple uart server/client\n\nThe simplest way of establishing a connection between two Bluetooth capable devices is using `UartBluetoothServer` and `UartBluetoothClient`, as it requires no knowledge of `bluez` or its interfaces.\n\nThe following code snippet implements functions to create a UART-GATT-server or connect to one with the existing name. The client emits a `Hello World` message, which is returned by the server.\n\nImport required classes:\n\n```js\n// \nconst { UartBluetoothServer, UartBluetoothClient } = require('@sorrir/bluetooth')\n```\n\nor\n\n```ts\nimport { UartBluetoothServer, UartBluetoothClient } from '@sorrir/bluetooth'\n```\n\nStart server:\n\n```js\nconst server = new UartBluetoothServer('SORRIR-Gatt-Server')\nserver.handleMessage = (message, sender) =\u003e {\n    console.log(\n        `received: ${JSON.stringify(\n            { msg: message, sender: sender })}`)\n    server.sendMessage(message)\n}\nawait server.start()\n```\n\nConnect to server:\n```js\nconst client = new UartBluetoothClient('SORRIR-Gatt-Server')\nclient.handleMessage = (message, sender) =\u003e {\n    console.log(\n        `received: ${JSON.stringify(\n            { msg: message, sender: sender })}`)\n}\nawait client.connect()\nawait client.sendMessage('Hello World')\n```\n\nMessages sent between the devices have the format\n```js\n{\n    msg: \u003cutf-8 encoded message\u003e\n    sender: \u003cpublic name of the senders adapter\u003e\n}\n```\nWhile the functionality of `UartBluetoothServer` and `UartBluetoothClient` might be expanded in the future, right now their use is limited to sending and receiving `string` messages.\n\n### Custom uart server/client\n\nIf the functionality of the former client and server is too basic, we can implement those ourselves.\n\nTo do that, we first need to initialize the central `bluez` object. It wraps around the system D-Bus and is required for the initialization of all interfaces.\n\n```js\n// create central bluez object\nconst bluez = await new Bluez().init()\n```\n\nAfterwards we can connect to the adapter, turn it on and get its address.\n\n```js\n// connect to adapter and power it on\n// '/org/bluez/hci0' is the default adapter on most devices\nlet adapter = await Adapter.connect(bluez, '/org/bluez/hci0')\nawait adapter.Powered.set(true)\n\n// adapter address\nlet address = await adapter.Address.get()\n\n// define message payload for later use\nconst json = {\n    msg: \"Hello World\",\n    sender: address\n}\n```\n\nNow we can discover and connect to the server as follows:\n\n```js\n// start discovery, wait until the discovery has started and then set\n// the discovery filter to only show BLE devices\nawait adapter.startDiscovery()\nawait adapter.Discovering.waitForValue(true)\nawait adapter.setDiscoveryFilter({ 'Transport': new Variant('s', 'le') })\n\n// find target device by name, connect to it\n// and wait until the connection is established\nlet device = await adapter.getDeviceByName('SORRIR-Gatt-Server')\nawait device.connect()\nawait device.Connected.waitForValue(true)\n\n// get service by its UUID\n// the given UUID is the one of the UART-service used\n// in the UartBluetoothServer\nawait device.ServicesResolved.waitForValue(true)\nlet service = await device.getService(\n    { UUID: '6e400001-b5a3-f393-e0a9-e50e24dcca9e' })\n\n// get write and notify characteristics from service\nlet writeCharacteristic =\n    await service.getCharacteristic({ Flags: 'write' })\nlet notifyCharacteristic =\n    await service.getCharacteristic({ Flags: 'notify' })\n\n// start notification and handle incoming messages\n// of notify characteristic\nawait notifyCharacteristic.startNotify()\nnotifyCharacteristic.ValueAsString.addListener((text) =\u003e {\n    console.log(text)\n})\n\n// write hello world message to write characteristic\nawait writeCharacteristic.writeString(JSON.stringify(json))\n```\n\nAlternatively, we could start the server instead:\n\n```js\n// get advertising and GATT-manager\nlet advertisingManager = await adapter.getAdvertisingManager()\nlet gattManager = await adapter.getGattManager()\n\n// create and register advertisement\nlet advertisement = new UartAdvertisement(bluez, 'SORRIR-Gatt-Server', 0)\nawait advertisingManager.registerAdvertisement(advertisement.path, {})\n\n// create and register application\nlet application = new UartApplication(bluez)\nawait gattManager.registerApplication(application.path, {})\n\n// get write and notify characteristics from application\nlet txCharacteristic = application.service.txCharacteristic\nlet rxCharacteristic = application.service.rxCharacteristic\n\n// answer incoming messages with hello world message\nrxCharacteristic.onMessage = (message) =\u003e {\n    console.log(message)\n    txCharacteristic.sendMessage(JSON.stringify(json))\n}       \n```\n\n## Interfaces\n\nInterfaces are the way that we can communicate with `bluez`. We have two different kinds of interfaces, which differ in who provides them:\n\n* `client interfaces` are provided and implemented by `bluez`. This means we, as the client, connect to and communicate with them via the D-Bus.\n\n* `host interfaces` are implemented by us. `bluez` connects to our custom interfaces which allows to created or own services or applications.\n\nAll interfaces share three different ingredients:\n\n* `properties` that can be set or get\n* `methods` that can be called by the user\n* `signals` that are called\n\n### Client interfaces\n\n`client interfaces` are provided by `bluez` itself and have well defined functionality. `client interfaces` are initialized with its classes' static `connect` method.\n\nSay for example you would want to connect to the `Adapter` interface:\n```js\nlet adapter = await Adapter.connect(bluez, 'org/bluez/hci0')\n```\n\n##### Properties\n\nThe `adapter` has multiple properties that can be either a `Property` or `ReadOnlyProperty`.\n\n`adapter.Powered` is a `Property`:\n```js\n// read property\nlet powered = await adapter.Powered.get()\n// write property\nawait adapter.Powered.set(true)\n```\nA `ReadOnlyProperty` is similar, however it misses the `set` method.\n\nAll properties emit an event whenever their value is changed:\n```js\n// wait until powered changes\nawait adapter.Powered.waitForChange()\n// wait until powered is set to true\nawait adapter.Powered.waitForValue(true)\n// do something on change\nadapter.Power.addListener(\n    (newValue) =\u003e { /* do something */ })\n```\n\n##### Methods\n\nMethods are as straightforward as calling them:\n```js\n// start discovery\nawait adapter.startDiscovery()\n```\nHowever, the meaning of `await` in this context needs to be clarified. It does not mean `wait until discovery has started`, but instead `wait until the method call is sent via the D-Bus`. In consequence, if you want to make sure the discovery really has started, you need to wait until the corresponding property has changed:\n```js\n// start discovery and wait until discovery started\nawait adapter.startDiscovery()\nawait adapter.Discovering.waitForValue(true)\n```\n\n##### Signals\n\n`Adapter` has no signals, however `DBusObjectManager` does. Signals, similarly to properties, emit an event whenever they are triggered:\n```js\n// do something on 'InterfacesAdded' signal call\ndBusObjectManager.InterfacesAdded.addListener(\n    (path, objects) =\u003e { /* do something */ })\n// do something on 'InterfacesRemoved' signal call\ndBusObjectManager.InterfacesRemoved.addListener(\n    (path, interfaceNames) =\u003e { /* do something */ })\n```\nThe parameters for the callback depend on the `Signal`.\n\n### Host interfaces\n\n`host interfaces` are implemented by the user. They are needed if you want to implement your own services or peripherals.\n\n#### Prerequisites\n\nImplementing `host interfaces` requires you to use Babel and enable the plugins `@babel/plugin-proposal-decorators`, as well as `@babel/plugin-proposal-class-properties`.\n\nAdditionally, it is recommended to use TypeScript for the implementation of `host interfaces`. While it is most likely possible to use the typescript compiler with the decorators as well, in the compilation of the package the plugin `@babel/plugin-transform-typescript` was used for code transpiling.\n\n#### Implementation\n\n`host interfaces` are classes that extend the base class `BaseHostInterface`. An example is `UartAdvertisement`:\n\n```ts\nclass UartAdvertisement extends BaseHostInterface {\n    LocalName: string\n    ServiceUUIDs: string[]\n    Includes: string[]\n    Type: string\n\n    constructor(bluez: Bluez, name: string, index: uint16 = 0) {\n        super(bluez,\n            // interface path\n            `/org/bluez/sorrir/advertisement${index}`,\n            // interface name\n            `org.bluez.LEAdvertisement1`,\n            // these are a list of properties of the interface\n            {\n                // define property via object with signature and value\n                'LocalName': { signature: 's', value: name },\n                // define property via Variant\n                'ServiceUUIDs': new Variant('as',\n                    ['6e400001-b5a3-f393-e0a9-e50e24dcca9e']),\n                'Includes': new Variant('as', [\"tx-power\"]),\n                'Type': new Variant('s', 'peripheral')\n            }\n        )\n        // this call writes the properties and exposes the interface\n        // to the bus. It needs to be called, otherwise the interface\n        // is invisible\n        this._init()\n    }\n\n    @method({ inSignature: '', outSignature: '' })\n    Release() {\n        console.log(\"released!\")\n    }\n}\n```\nEvery `host interface` needs to have a well defined `path` and `name`. The name corresponds to the interface that is implemented, which is defined by `bluez`. It provides possible properties, methods and signals. Unfortunately, as the implementation of host interfaces is in an early state in this package, there is no comprehensive list of interfaces and their paths that can be implemented. For now, see the [bluez documentation](https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc) in that matter.\n\n##### Properties\n\nProperties are initialized from within the constructor as parameter of the `super` call. After `this._init()` has been called, they can be accessed just like regular class parameters. Host properties are initialized in the format\n```ts\n{\n    signature: string // D-Bus signature of of the property\n    value: dBusType // initial value \n    valueTransform?: ((base: any) =\u003e dBusType) // transformation from actual value to D-Bus understandable value\n}\n```\nThe `signature` tells the D-Bus which type the property is of. For example, `s` is a String, `b` a Boolean. For a complete explanation of possible signatures, look into the [D-Bus specification](https://dbus.freedesktop.org/doc/dbus-specification.html#type-system).\n\n`valueTransform` allows the variable to have a different native type to the actual D-Bus compliant value. For example, if you would want to save an index as `number`, but the interfaces specification requires a `string` prefixed by \"index\", you can do this:\n```ts\n{\n    signature: 's' // D-Bus value is actually a string\n    value: 0 // initial value is number\n    valueTransform: (i) =\u003e `index${i}` // transformation from number to string\n}\n```\nIf no `valueTransform` is provided, you can also provide a `Variant`, as done in the above example interface.\n\n##### Methods\n\nMethods are declared with the decorator `@method`. They require an input and output signature which correspond to the input parameters and the return values.\n\n```ts\n@method({ inSignature: 's', outSignature: 'as' })\nStringAsArray(s: string) {\n    return s.split(\"\")\n}\n```\n\nMethods can be overwritten if the already implemented interface is extended further, however `this._init()` has to be called again in the constructor of the expanding class, otherwise changes are not reflected.\n\n##### Signals\n\nMethods are declared with the decorator `@signal`. They require a signature which corresponds to the input parameters.\n\n```ts\n@signal({ signature: 's' })\nPrintString(s: string) {\n    console.log(s)\n}\n```\n\nLike methods, signals can be overwritten but require a re-call of `this._init()` in the constructor of the expanding class.\n\n## TODO\n\n* Better documentation of code\n* Better implementation of host interfaces\n* Unify implementation of host and client interfaces\n* Implement test cases\n* Implement further abstractions\n* ...\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsorrir%2Fnode-bluetooth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsorrir%2Fnode-bluetooth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsorrir%2Fnode-bluetooth/lists"}