SimpleBLE

Usage

Install and use the SimpleGoBLE Go binding.

SimpleGoBLE exposes SimpleBLE's Bluetooth client and GATT hosting APIs to Go on Windows, macOS, and Linux, with support for amd64 and arm64.

Install

Use Go 1.25 or newer:

go get github.com/simpleble/simplegoble

Import github.com/simpleble/simplegoble in your application, then build:

go build ./...

Platform requirements

Linux uses BlueZ, D-Bus, and the system C++ runtime. Windows uses the Bluetooth stack and Microsoft C++ runtime. macOS apps need Bluetooth usage descriptions in Info.plist. Follow the platform's Bluetooth permission setup.

SimpleGoBLE uses a writable user cache directory for runtime libraries.

List adapters

package main

import (
    "fmt"
    "log"

    ble "github.com/simpleble/simplegoble"
)

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    adapters, err := ble.GetAdapters()
    if err != nil {
        return err
    }
    defer func() {
        for _, adapter := range adapters {
            _ = adapter.Close()
        }
    }()
    for _, adapter := range adapters {
        name, err := adapter.Identifier()
        if err != nil {
            return err
        }
        fmt.Println(name)
    }
    return nil
}

Use GetNativeBackend(), GetDonglBackend(), or GetSimulatorBackend() and backend.Adapters() for the adapters of one backend, or GetBackends() to list them all. Close every returned backend and adapter. Adapters have independent lifetimes from their backend. Copies of a wrapper share ownership, so closing a copy closes the original as well.

As with os.File, a handle that becomes unreachable without Close is eventually released by the garbage collector. This is a safety net, not a replacement for Close. A handle with registered callbacks stays alive until they are cleared or it is closed, and a local host is only released by Close because releasing it stops hosting.

Configure backends

ble.Config holds process-wide settings, grouped by backend: SimpleBluez (Linux), WinRT (Windows), CoreBluetooth (macOS), and Dongl. Set them at startup, before the first GetBackends, GetAdapters, or BluetoothEnabled call; changes after a backend starts may not take effect.

if err := ble.Config.SimpleBluez.SetConnectionTimeout(5 * time.Second); err != nil {
    return err
}

Each setting has a getter and a setter, such as UseSystemBus and SetUseSystemBus; timeouts use time.Duration. Reset restores one backend's defaults, and ble.Config.ResetAll() restores them all. Configuration is not synchronized, so set it before other goroutines use SimpleGoBLE.

Use a Dongl

A Dongl is a USB Bluetooth adapter made for SimpleBLE. Its backend is on by default; take the adapters of the Dongl backend:

dongl, err := ble.GetDonglBackend()
if err != nil {
    return err
}
defer dongl.Close()
adapters, err := dongl.Adapters() // Close each adapter when finished.
if err != nil {
    return err
}
if len(adapters) == 0 {
    return errors.New("no Dongl found")
}

A Dongl adapter works like any other adapter. Pairing through it happens without the operating system, so your application answers pairing requests. Register the callbacks your device needs on the peripheral before Connect:

  • SetCallbackOnPasskeyRequest(func() (string, bool)): return the six digits the user typed and true, or false to reject.
  • SetCallbackOnPasskeyDisplay(func(string)): show the passkey so the user can type it on the device.
  • SetCallbackOnNumericComparison(func(string) bool): return true only if the device shows the same six digits.
if err := peripheral.SetCallbackOnNumericComparison(func(passkey string) bool {
    fmt.Printf("Does the device show %s? [y/N] ", passkey)
    answer, _ := bufio.NewReader(os.Stdin).ReadString('\n')
    return strings.TrimSpace(answer) == "y"
}); err != nil {
    return err
}
if err := peripheral.Connect(); err != nil {
    return err
}

Passkey request and numeric comparison handlers run synchronously on SimpleBLE's pairing thread and can block while the user answers. Do not call BLE methods, including Close, from them. A missing handler, a panic, or a passkey other than six ASCII digits rejects pairing. The display handler follows the usual serial dispatch rules. Pass nil to clear a callback. Peripherals of other adapters return OperationNotSupported.

The dongl example scans with a Dongl and answers pairing requests from the terminal.

Power and scan control

Adapters provide IsPowered, PowerOn, PowerOff, ScanStart, ScanStop, ScanIsActive, and ScanFor(time.Duration). Power control follows the platform's capabilities. ScanFor waits synchronously while other Go goroutines remain runnable. Use ScanStart and ScanStop when your application controls timing.

Register power and scan events with SetCallbackOnPowerOn, SetCallbackOnPowerOff, SetCallbackOnScanStart, and SetCallbackOnScanStop. Pass nil to clear a callback. Handlers run serially on a Go worker and can call adapter methods, including Close. Keep handlers short so later events can run. Native callback slots are shared across handles for the same adapter; the latest registration wins.

Discover peripherals

After scanning, call adapter.ScanGetResults() to retrieve peripherals. GetPairedPeripherals and GetConnectedPeripherals enumerate the adapter's paired and connected devices. Each result owns an independent handle; close all of them when finished. A peripheral remains usable after its adapter closes.

Use SetCallbackOnScanFound(func(*ble.Peripheral)) and SetCallbackOnScanUpdated(func(*ble.Peripheral)) for live advertisement events. Every invocation transfers a new peripheral handle to your handler, which must close it or retain it for later use. Pass nil to clear either callback.

On macOS, adapter.RetrieveCachedPeripherals(identifiers) returns peripherals that CoreBluetooth already knows, by the identifiers Address returned earlier, without scanning. Unknown identifiers are skipped, and a returned peripheral may be out of range. Other platforms return OperationNotSupported.

Peripheral methods expose Identifier, Address, AddressType, RSSI, TxPower, IsConnectable, and ManufacturerData. Services() returns advertised service data before connection and discovered GATT services after connection. All returned metadata and buffers are Go-owned and can be retained.

Connect and use GATT

Use Connect and Disconnect to control the connection, IsConnected and IsPaired to check state, Unpair to remove pairing, and MTU to query the negotiated MTU. Register connection events with SetCallbackOnConnected and SetCallbackOnDisconnected.

For a peripheral obtained during scanning, read its battery characteristic:

func readBattery(peripheral *ble.Peripheral) ([]byte, error) {
    if err := peripheral.Connect(); err != nil {
        return nil, err
    }
    defer peripheral.Disconnect()
    return peripheral.Read("0000180f-0000-1000-8000-00805f9b34fb", "00002a19-0000-1000-8000-00805f9b34fb")
}

Services includes characteristic capabilities and descriptor UUIDs. Use Read, WriteRequest, and WriteCommand for characteristics, and ReadDescriptor and WriteDescriptor for descriptors. UUID arguments are canonical 128-bit UUIDs, in either case. Empty writes are supported. Keep an input byte slice unchanged until the write returns.

Subscribe using Notify(service, characteristic, func([]byte)) or Indicate. Each event supplies a Go-owned slice that your handler can retain. Call Unsubscribe to stop the subscription. Handlers follow the same serial dispatch rules as adapter callbacks and may call peripheral methods, including Close. Connection and subscription state are shared by handles for the same device; the latest callback registration for a slot wins.

Methods are safe for concurrent use. Close waits for native calls and discards queued callbacks; a handler already selected may finish after Close returns. Closing a handle does not change power, scanning, or connection state. Call Disconnect and Unsubscribe explicitly to end those operations.

Host local GATT services

Call adapter.CreateLocalPeripheral() to create a host. Add primary services with host.AddService(uuid) and characteristics with service.AddCharacteristic(uuid, capabilities). Combine CapabilityRead, CapabilityWriteRequest, CapabilityWriteCommand, CapabilityNotify, and CapabilityIndicate using bitwise OR.

SimpleGoBLE does not restrict repeated service or characteristic UUIDs, such as several HID Report characteristics in one service; support follows the native backend. The PLAIN backend rejects them.

Add advertisement UUIDs with AddAdvertisedService. Configure the GATT layout before calling Start, which publishes services and starts advertising. Use IsStarted and IsAdvertising to inspect state, and Stop to end hosting. After stopping, use RemoveAllServices to rebuild the layout. Native backends report unsupported operations through the usual error classifications.

SetAdvertisedManufacturerData(companyID, data) adds manufacturer specific data to the advertisement, and SetConnectable(false) makes it non-connectable, for example to advertise a beacon. Configure both before Start.

On macOS and Linux, host.SetAdvertisementLocalName(name) sets the local name in the advertisement before Start, and ClearAdvertisementLocalName removes it. This changes only the advertisement, not the system Bluetooth name. Other platforms return OperationNotSupported.

characteristic.SetValue([]byte) updates the stored value and publishes to subscribed clients on a best-effort basis. Value() returns a Go-owned copy of the stored bytes. Empty values and binary data are supported.

Register SetCallbackOnWrite(func([]byte)) to observe incoming writes. The stored value is updated before the handler runs. Write handlers receive Go-owned bytes and may call SetValue to echo them to subscribers. SetCallbackOnSubscribed observes the first client subscribing; SetCallbackOnUnsubscribed observes the last client unsubscribing. Hosts expose SetCallbackOnClientConnected and SetCallbackOnClientDisconnected, passing a Go-owned client address or platform identifier. These event handlers follow the same serial dispatch rules as the client API and may call their wrapper's methods or Close.

Use SetCallbackOnRead(func() []byte) for a dynamic read value. It runs synchronously on the native calling thread, so return promptly and use only application data: do not call BLE methods from a read callback. Replacing a read handler or closing a characteristic from one returns OperationNotSupported. Go copies the returned bytes. Pass nil to restore the stored value; Value() always reads stored bytes without calling the dynamic handler.

Every local host, service, and characteristic returned by creation or enumeration owns a handle that must be closed. Copies of a wrapper share ownership; Services() and Characteristics() return independent handles. Closing a service or characteristic does not remove it from the hosted GATT layout. Closing a characteristic clears its dynamic read callback if it still owns the slot, preserves a replacement registered through another handle, and expires its event callbacks. Closing the host stops hosting. Stop the host and close all local handles when finished. Removing services invalidates their native configuration handles; subsequent operations report the native error.

The peripheral example hosts a service that echoes incoming writes.

Errors

Check classifications with errors.Is(err, ble.OperationNotSupported). Use errors.As with *ble.Error to inspect the native error code and diagnostic message. Methods on closed or zero-value wrappers return ble.ErrClosed.

See the Go API reference for method contracts and the examples for runnable applications.

Versions

SimpleGoBLE uses SimpleBLE's stable vX.Y.Z and development vX.Y.Z-devN tags. Use @main for the latest development build; Go prefers stable releases for @latest. Each module includes VERSION and SOURCE_REVISION.

For binding development and local builds, see the contributor guide.

On this page