golang构建蓝牙低能耗外设插件库gatt的使用

Golang构建蓝牙低能耗外设插件库gatt的使用

概述

gatt包提供了蓝牙低能耗(BLE) GATT(通用属性配置文件)的实现。GATT是用于编写BLE外设(服务器)和中心设备(客户端)的协议。

作为外设,您可以:

  • 创建服务、特征和描述符
  • 广播广告
  • 接受连接
  • 处理请求

作为中心设备,您可以:

  • 扫描设备
  • 连接设备
  • 发现服务
  • 发起请求

环境设置

gatt支持Linux和OS X平台。

Linux平台设置

  1. 首先确保您的BLE设备已关闭:
sudo hciconfig
sudo hciconfig hci0 down  # 或使用您想使用的hci设备
  1. 如果您有BlueZ 5.14+,停止内置的蓝牙服务:
sudo service bluetooth stop
  1. 因为gatt程序需要管理网络设备,必须以root权限运行或授予相应能力:
sudo <executable>
# 或者
sudo setcap 'cap_net_raw,cap_net_admin=eip' <executable>
<executable>

使用示例

构建和运行示例

Go是编译型语言,需要先构建示例:

# 构建示例服务器
go build examples/server.go
# 启动示例服务器
sudo ./server

或者使用go run一步完成构建和运行:

# 构建并运行示例服务器
sudo go run examples/server.go

发现和探索设备

发现周围的外设:

sudo go run examples/discoverer.go

连接并探索外设设备:

sudo go run examples/explorer.go <peripheral ID>

交叉编译到ARM设备

# 为ARMv5目标设备构建服务器示例
GOARCH=arm GOARM=5 GOOS=linux go build examples/server.go
cp server <target device>
# 在目标设备上启动服务器
sudo ./server

示例代码

服务器示例代码

package main

import (
	"log"
	"time"

	"github.com/paypal/gatt"
	"github.com/paypal/gatt/examples/option"
	"github.com/paypal/gatt/examples/service"
)

func main() {
	d, err := gatt.NewDevice(option.DefaultServerOptions...)
	if err != nil {
		log.Fatalf("Failed to open device, err: %s", err)
	}

	// 注册可选的处理程序
	d.Handle(
		gatt.CentralConnected(func(c gatt.Central) { 
			log.Println("Connect: ", c.ID()) 
		}),
		gatt.CentralDisconnected(func(c gatt.Central) { 
			log.Println("Disconnect: ", c.ID()) 
		}),
	)

	// 启动设备管理
	onStateChanged := func(d gatt.Device, s gatt.State) {
		log.Printf("State: %s", s)
		switch s {
		case gatt.StatePoweredOn:
			// 设置GAP和GATT服务
			s1 := service.NewGapService("Gopher")
			s2 := service.NewGattService()
			d.AddService(s1)
			d.AddService(s2)

			// 添加自定义服务
			s3 := service.NewBatteryService()
			d.AddService(s3)

			// 开始广播
			adv := gatt.NewAdvertisement("gopher", []gatt.UUID{s3.UUID()})
			d.Advertise(adv)
		default:
		}
	}

	d.Init(onStateChanged)
	select {
	case <-time.After(60 * time.Minute):
	}
}

发现器示例代码

package main

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"

	"github.com/paypal/gatt"
	"github.com/paypal/gatt/examples/option"
)

func onStateChanged(d gatt.Device, s gatt.State) {
	fmt.Println("State:", s)
	switch s {
	case gatt.StatePoweredOn:
		fmt.Println("Scanning...")
		d.Scan([]gatt.UUID{}, false)
		return
	default:
		d.StopScanning()
	}
}

func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
	fmt.Printf("\nPeripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name())
	fmt.Println("  Local Name        =", a.LocalName)
	fmt.Println("  TX Power Level    =", a.TxPowerLevel)
	fmt.Println("  Manufacturer Data =", a.ManufacturerData)
	fmt.Println("  Service Data      =", a.ServiceData)
}

func main() {
	d, err := gatt.NewDevice(option.DefaultClientOptions...)
	if err != nil {
		log.Fatalf("Failed to open device, err: %s\n", err)
		return
	}

	// 注册处理程序
	d.Handle(
		gatt.PeripheralDiscovered(onPeriphDiscovered),
	)
	d.Init(onStateChanged)

	// 等待Ctrl+C退出
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	<-sig

	fmt.Println("Done")
}

注意事项

  1. 某些BLE中心设备(特别是iOS)可能会积极缓存先前连接的结果。如果您更改了服务或特征,可能需要重启其他设备才能获取更改。

  2. 目前OS X版本不支持订阅指示(Indications)。

  3. gatt使用HCI_CHANNEL_USER,一旦gatt打开了设备,其他程序就无法访问它。

  4. 对于更细粒度的设备控制,特别是Linux平台,可以参考examples/server_lnx.go中的Option用法。


更多关于golang构建蓝牙低能耗外设插件库gatt的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang构建蓝牙低能耗外设插件库gatt的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang 构建蓝牙低能耗外设插件库 gatt 的使用

gatt 是一个 Go 语言实现的蓝牙低能耗 (BLE) 库,用于构建 BLE 外设(Peripheral)和中心设备(Central)。下面我将详细介绍如何使用 gatt 库构建 BLE 外设。

安装 gatt 库

首先需要安装 gatt 库:

go get github.com/paypal/gatt

注意:gatt 在 Linux 上需要 BlueZ 支持,在 macOS 上需要 Xcode 命令行工具。

基本外设示例

下面是一个简单的 BLE 外设示例,创建一个可被发现的服务和特征:

package main

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"time"

	"github.com/paypal/gatt"
	"github.com/paypal/gatt/examples/option"
)

func main() {
	// 初始化设备
	d, err := gatt.NewDevice(option.DefaultServerOptions...)
	if err != nil {
		log.Fatalf("Failed to open device, err: %s", err)
	}

	// 注册设备事件处理
	d.Handle(
		gatt.CentralConnected(func(c gatt.Central) {
			fmt.Println("Central connected:", c.ID())
		}),
		gatt.CentralDisconnected(func(c gatt.Central) {
			fmt.Println("Central disconnected:", c.ID())
		}),
	)

	// 初始化服务
	onStateChanged := func(d gatt.Device, s gatt.State) {
		fmt.Printf("State: %s\n", s)
		switch s {
		case gatt.StatePoweredOn:
			// 设置外设名称
			d.SetName("MyGattDevice")
			fmt.Println("Device name set to 'MyGattDevice'")

			// 添加服务
			s1 := gatt.NewService(gatt.MustParseUUID("09fc95c0-c111-11e3-9904-0002a5d5c51b"))
			s1.AddCharacteristic(gatt.MustParseUUID("11fac9e0-c111-11e3-9246-0002a5d5c51b")).HandleReadFunc(
				func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) {
					fmt.Println("Characteristic read")
					rsp.Write([]byte("Hello from Gatt!"))
				},
			)
			d.AddService(s1)

			// 开始广播
			adv := &gatt.AdvPacket{}
			adv.AddFlags(0x06) // LE General Discoverable | BR/EDR Not Supported
			adv.AddCompleteName("MyGattDevice")
			adv.AddUUIDFit(s1.UUID())
			d.Advertise(adv)
		default:
		}
	}

	d.Init(onStateChanged)

	// 处理中断信号
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)
	<-sig

	fmt.Println("\nStopping...")
	d.StopAdvertising()
	d.RemoveAllServices()
}

关键组件说明

1. 设备初始化

d, err := gatt.NewDevice(option.DefaultServerOptions...)

option.DefaultServerOptions 提供了默认的设备配置选项,包括:

  • 设备角色设置为 Peripheral
  • 连接参数设置
  • 其他底层配置

2. 服务与特征创建

s1 := gatt.NewService(gatt.MustParseUUID("09fc95c0-c111-11e3-9904-0002a5d5c51b"))
s1.AddCharacteristic(gatt.MustParseUUID("11fac9e0-c111-11e3-9246-0002a5d5c51b"))
  • NewService 创建一个新的服务
  • AddCharacteristic 为服务添加特征
  • UUID 可以使用标准 UUID 或自定义 UUID

3. 特征操作处理

.HandleReadFunc(
	func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) {
		rsp.Write([]byte("Hello from Gatt!"))
	},
)

可以处理的特征操作包括:

  • HandleReadFunc - 处理读取请求
  • HandleWriteFunc - 处理写入请求
  • HandleNotifyFunc - 处理通知/指示

4. 广播配置

adv := &gatt.AdvPacket{}
adv.AddFlags(0x06) // LE General Discoverable | BR/EDR Not Supported
adv.AddCompleteName("MyGattDevice")
adv.AddUUIDFit(s1.UUID())
d.Advertise(adv)
  • AddFlags 设置广播标志
  • AddCompleteName 添加完整设备名称
  • AddUUIDFit 添加服务 UUID
  • Advertise 开始广播

完整功能示例

下面是一个更完整的示例,包含读写特征和通知:

package main

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"sync"

	"github.com/paypal/gatt"
	"github.com/paypal/gatt/examples/option"
)

var (
	notifyData = []byte{0}
	notifyMu   sync.Mutex
)

func main() {
	d, err := gatt.NewDevice(option.DefaultServerOptions...)
	if err != nil {
		log.Fatalf("Failed to open device, err: %s", err)
	}

	// 处理连接事件
	d.Handle(
		gatt.CentralConnected(func(c gatt.Central) {
			fmt.Println("Connected:", c.ID())
		}),
		gatt.CentralDisconnected(func(c gatt.Central) {
			fmt.Println("Disconnected:", c.ID())
		}),
	)

	onStateChanged := func(d gatt.Device, s gatt.State) {
		fmt.Printf("State: %s\n", s)
		switch s {
		case gatt.StatePoweredOn:
			// 创建服务
			s1 := gatt.NewService(gatt.MustParseUUID("180F")) // Battery Service

			// 添加可读特征
			c1 := s1.AddCharacteristic(gatt.MustParseUUID("2A19")) // Battery Level
			c1.HandleReadFunc(func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) {
				fmt.Println("Battery Level read")
				rsp.Write([]byte{50}) // 返回50%电量
			})

			// 添加可写特征
			c2 := s1.AddCharacteristic(gatt.MustParseUUID("0001"))
			c2.HandleWriteFunc(func(r gatt.Request, data []byte) (status byte) {
				fmt.Printf("Received write: %X\n", data)
				return gatt.StatusSuccess
			})

			// 添加可通知特征
			c3 := s1.AddCharacteristic(gatt.MustParseUUID("0002"))
			c3.HandleNotifyFunc(
				func(r gatt.Request, n gatt.Notifier) {
					fmt.Println("Notification started")
					for !n.Done() {
						notifyMu.Lock()
						n.Write(notifyData)
						notifyMu.Unlock()
						// 模拟数据变化
						notifyData[0]++
					}
					fmt.Println("Notification stopped")
				},
			)

			d.AddService(s1)

			// 开始广播
			adv := &gatt.AdvPacket{}
			adv.AddFlags(0x06)
			adv.AddCompleteName("BatteryDemo")
			adv.AddUUIDFit(s1.UUID())
			d.Advertise(adv)

		default:
		}
	}

	d.Init(onStateChanged)

	// 等待中断信号
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt)
	<-sig

	fmt.Println("\nStopping...")
	d.StopAdvertising()
}

注意事项

  1. 权限问题:在 Linux 上运行需要蓝牙管理权限,通常需要以 root 或具有适当权限的用户运行。

  2. UUID 格式:可以使用完整 UUID 或短 UUID(16-bit 或 32-bit)。

  3. 广播数据限制:广播数据包大小有限制(通常31字节),需合理规划广播内容。

  4. 跨平台支持:gatt 在 Linux 和 macOS 上支持较好,Windows 支持有限。

  5. 并发处理:特征操作可能并发发生,需要适当的同步机制。

通过以上示例和说明,您应该能够开始使用 gatt 库构建自己的 BLE 外设应用。根据实际需求,您可以扩展服务、特征和相应的处理逻辑。

回到顶部