/
discover.go
142 lines (131 loc) · 3.62 KB
/
discover.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package ble
import (
"fmt"
"log"
"time"
"github.com/godbus/dbus"
)
func (conn *Connection) addMatch(rule string) error {
return conn.bus.BusObject().Call(
"org.freedesktop.DBus.AddMatch",
0,
rule,
).Err
}
func (conn *Connection) removeMatch(rule string) error {
return conn.bus.BusObject().Call(
"org.freedesktop.DBus.RemoveMatch",
0,
rule,
).Err
}
// DiscoveryTimeoutError indicates that discovery has timed out.
type DiscoveryTimeoutError []string
func (e DiscoveryTimeoutError) Error() string {
return fmt.Sprintf("discovery timeout %v", []string(e))
}
// Discover puts the adapter in discovery mode,
// waits for the specified timeout to discover one of the given UUIDs,
// and then stops discovery mode.
func (adapter *blob) Discover(timeout time.Duration, uuids ...string) error {
conn := adapter.conn
signals := make(chan *dbus.Signal)
defer close(signals)
conn.bus.Signal(signals)
defer conn.bus.RemoveSignal(signals)
rule := "type='signal',interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'"
err := adapter.conn.addMatch(rule)
if err != nil {
return err
}
defer conn.removeMatch(rule)
err = adapter.SetDiscoveryFilter(uuids...)
if err != nil {
return err
}
err = adapter.StartDiscovery()
if err != nil {
return err
}
defer adapter.StopDiscovery()
var t <-chan time.Time
if timeout != 0 {
t = time.After(timeout)
}
return adapter.discoverLoop(uuids, signals, t)
}
func (adapter *blob) discoverLoop(uuids []string, signals <-chan *dbus.Signal, timeout <-chan time.Time) error {
for {
select {
case s := <-signals:
switch s.Name {
case interfacesAdded:
if adapter.discoveryComplete(s, uuids) {
return nil
}
default:
log.Printf("%s: unexpected signal %s", adapter.Name(), s.Name)
}
case <-timeout:
return DiscoveryTimeoutError(uuids)
}
}
}
func (adapter *blob) discoveryComplete(s *dbus.Signal, uuids []string) bool {
props := interfaceProperties(s)
if props == nil {
log.Printf("%s: skipping signal %s with no device interface", adapter.Name(), s.Name)
return false
}
addr, ok := props["Address"].Value().(string)
if !ok {
addr = "[unknown address]"
}
name, ok := props["Name"].Value().(string)
if !ok {
name = "[unknown name]"
}
services, ok := props["UUIDs"].Value().([]string)
if !ok {
services = nil
}
if !UUIDsInclude(services, uuids) {
log.Printf("%s: skipping signal for %s (%s) without matching UUIDs", adapter.Name(), addr, name)
log.Printf("%s: wanted %v, got %v", adapter.Name(), uuids, services)
return false
}
log.Printf("%s: discovered %s (%s)", adapter.Name(), addr, name)
return true
}
// If the InterfacesAdded signal contains deviceInterface,
// return the corresponding properties, otherwise nil.
// See http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
func interfaceProperties(s *dbus.Signal) Properties {
var dict map[string]Properties
err := dbus.Store(s.Body[1:2], &dict)
if err != nil {
log.Print(err)
return nil
}
return dict[deviceInterface]
}
// Discover initiates discovery for a LE peripheral with the given address (if nonempty), advertising the given UUIDs.
// It waits for at most the specified timeout, or indefinitely if timeout = 0.
func (conn *Connection) Discover(timeout time.Duration, address Address, uuids ...string) (Device, error) {
adapter, err := conn.GetAdapter()
if err != nil {
return nil, err
}
err = adapter.Discover(timeout, uuids...)
if err != nil {
return nil, err
}
err = conn.Update()
if err != nil {
return nil, err
}
if address != "" {
return conn.GetDeviceByAddress(address)
}
return conn.GetDeviceByUUID(uuids...)
}