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/simplegobleImport 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 andtrue, orfalseto reject.SetCallbackOnPasskeyDisplay(func(string)): show the passkey so the user can type it on the device.SetCallbackOnNumericComparison(func(string) bool): returntrueonly 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.
