PageRenderTime 36ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/bluez/gatt_tool.go

https://gitlab.com/brucealdridge/driver-go-blecombined
Go | 162 lines | 103 code | 46 blank | 13 comment | 15 complexity | 34e58a8cebb577987864648dfac56091 MD5 | raw file
  1. // This package is a wrapper for the bluez gatt command
  2. // which we are using as an interum measure to enable
  3. // buzzing the sticknfind tags.
  4. package bluez
  5. import (
  6. "bufio"
  7. "bytes"
  8. "encoding/hex"
  9. "fmt"
  10. "os/exec"
  11. "regexp"
  12. "strings"
  13. "github.com/davecgh/go-spew/spew"
  14. "github.com/juju/loggo"
  15. )
  16. var (
  17. log = loggo.GetLogger("bluez")
  18. charRegex = regexp.MustCompile("handle = (?P<handle>[0-9a-fx]+), char properties = (?P<char_props>[0-9a-fx]+), char value handle = (?P<char_value_handle>[0-9a-fx]+), uuid = (?P<uuid>[0-9a-f-]+)")
  19. readRegex = regexp.MustCompile(`Characteristic value\/descriptor: ([0-9a-f ]+)`)
  20. )
  21. // gatttool -b F6:5F:20:4C:B0:DB -t random -l medium --char-write -a 0x002f -n 0103
  22. const (
  23. bluezGattPath = "/usr/bin/gatttool"
  24. AddrTypePublic = "public"
  25. AddrTypeRandom = "random"
  26. )
  27. // GattCmd this is the handle to a bluez gatt cmd.
  28. type GattCmd struct {
  29. baddr, addrType string
  30. }
  31. // Characteristic holds the attributes needed to address a characteristic via the api.
  32. type Characteristic struct {
  33. UUID string
  34. Handle string // use this to READ
  35. CharValueHandle string // use this to WRITE
  36. }
  37. // ReadCharacteristics query a device for it's characteristics
  38. func (gc *GattCmd) ReadCharacteristics() ([]*Characteristic, error) {
  39. chars := []*Characteristic{}
  40. data, err := run(bluezGattPath, "-b", gc.baddr, "-t", gc.addrType, "-l", "medium", "--characteristics")
  41. if err != nil {
  42. return chars, err
  43. }
  44. scanner := bufio.NewScanner(strings.NewReader(data))
  45. for scanner.Scan() {
  46. params := getAttributes(scanner.Text())
  47. c := &Characteristic{
  48. UUID: params["uuid"],
  49. Handle: params["handle"],
  50. CharValueHandle: params["char_value_handle"],
  51. }
  52. chars = append(chars, c)
  53. log.Infof(spew.Sprintf("%#v", c))
  54. }
  55. return chars, nil
  56. }
  57. // ReadCharacteristic connect to a ble device and read the caractersitic using the handle
  58. func (gc *GattCmd) ReadCharacteristic(handle string) ([]byte, error) {
  59. payload := []byte{}
  60. data, err := run(bluezGattPath, "-b", gc.baddr, "-t", gc.addrType, "-l", "medium", "--char-read", "-a", handle)
  61. if err != nil {
  62. return payload, err
  63. }
  64. // extract the value
  65. value := decodeValue(data)
  66. // strip spaces
  67. value = strings.Replace(value, " ", "", -1)
  68. return hex.DecodeString(value)
  69. }
  70. // WriteCharacteristic connect to a ble device and write a value to the caractersitic using the handle
  71. func (gc *GattCmd) WriteCharacteristic(handle, value string) error {
  72. _, err := run(bluezGattPath, "-b", gc.baddr, "-t", gc.addrType, "-l", "medium", "--char-write", "-a", handle, "-n", value)
  73. return err
  74. }
  75. // NewGattCmd create a gatt cmd handler.
  76. func NewGattCmd(baddr, addrType string) (error *GattCmd) {
  77. // TODO is this a valid address / type
  78. return &GattCmd{baddr, addrType}
  79. }
  80. func run(cmd string, params ...string) (string, error) {
  81. cmdExec := exec.Command(cmd, params...)
  82. log.Infof("exec %s %v", cmd, params)
  83. var out bytes.Buffer
  84. cmdExec.Stdout = &out
  85. cmdExec.Stderr = &out
  86. err := cmdExec.Run()
  87. if err != nil {
  88. return out.String(), err
  89. }
  90. log.Debugf("run: %q\n", out.String())
  91. if strings.Contains(out.String(), "Connection refused") {
  92. return out.String(), fmt.Errorf("Connection refused.")
  93. }
  94. return out.String(), nil
  95. }
  96. func getAttributes(line string) map[string]string {
  97. matches := charRegex.FindAllStringSubmatch(line, -1)
  98. if matches == nil {
  99. return nil
  100. }
  101. params := make(map[string]string)
  102. for i, attr := range charRegex.SubexpNames() {
  103. if attr == "" {
  104. continue
  105. }
  106. params[attr] = matches[0][i]
  107. }
  108. return params
  109. }
  110. func decodeValue(line string) string {
  111. m := readRegex.FindStringSubmatch(line)
  112. if len(m) == 2 {
  113. return m[1]
  114. }
  115. return ""
  116. }