/
base.go
226 lines (196 loc) · 5.14 KB
/
base.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/*
Package ble provides functions to discover, connect, pair,
and communicate with Bluetooth Low Energy peripheral devices.
This implementation uses the BlueZ D-Bus interface, rather than sockets.
It is similar to github.com/adafruit/Adafruit_Python_BluefruitLE
*/
package ble
import (
"fmt"
"io"
"time"
"github.com/godbus/dbus"
)
const (
objectManager = "org.freedesktop.DBus.ObjectManager"
callTimeout = 5 * time.Second
)
// Use type aliases for these subtypes so the reflection
// used by dbus.Store() works correctly.
type (
// Object represents a managed D-Bus object
// as a map from interface names to properties.
Object = map[string]Properties
// Properties represents a dbus interface
// as a map from property names to values (D-Bus variants).
Properties = map[string]dbus.Variant
// Connection represents a D-Bus connection.
Connection struct {
bus *dbus.Conn
objects map[dbus.ObjectPath]Object
}
// Address represents a MAC address.
Address string
)
// Open opens a connection to the system D-Bus
func Open() (*Connection, error) {
bus, err := dbus.SystemBus()
if err != nil {
return nil, err
}
conn := Connection{bus: bus}
err = conn.Update()
if err != nil {
conn.Close()
return nil, err
}
return &conn, nil
}
// Close closes the D-Bus connection.
func (conn *Connection) Close() {
conn.bus.Close()
}
// Update gets all objects and properties.
// See http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
func (conn *Connection) Update() error {
obj := conn.bus.Object("org.bluez", "/")
call := obj.Call(dot(objectManager, "GetManagedObjects"), 0)
return call.Store(&conn.objects)
}
// The iterObjects function applies a function of type objectProc to
// each object in the cache. It should return true if the iteration
// should stop, false if it should continue.
type objectProc func(dbus.ObjectPath, Object) bool
func (conn *Connection) iterObjects(proc objectProc) {
for path, dict := range conn.objects {
if proc(path, dict) {
return
}
}
}
// Print prints the objects in the cache.
func (conn *Connection) Print(w io.Writer) {
conn.iterObjects(func(path dbus.ObjectPath, dict Object) bool {
return printObject(w, path, dict)
})
}
func printObject(w io.Writer, path dbus.ObjectPath, dict Object) bool {
fmt.Fprintln(w, path)
for iface, props := range dict {
printProperties(w, iface, props)
}
fmt.Fprintln(w)
return false
}
// BaseObject is the interface satisfied by bluez D-Bus objects.
type BaseObject interface {
Conn() *Connection
Path() dbus.ObjectPath
Interface() string
Name() string
Print(io.Writer)
}
type blob struct {
conn *Connection
path dbus.ObjectPath
iface string
properties Properties
object dbus.BusObject
}
// Conn returns the object's D-Bus connection.
func (obj *blob) Conn() *Connection {
return obj.conn
}
// Path returns the object's D-Bus path.
func (obj *blob) Path() dbus.ObjectPath {
return obj.path
}
// Interface returns the object's D-Bus interface name.
func (obj *blob) Interface() string {
return obj.iface
}
// Name returns the object's name.
func (obj *blob) Name() string {
name, ok := obj.properties["Name"].Value().(string)
if !ok {
return string(obj.path)
}
return name
}
func (obj *blob) callv(method string, args ...interface{}) *dbus.Call {
c := obj.object.Go(dot(obj.iface, method), 0, nil, args...)
if c.Err == nil {
select {
case <-c.Done:
case <-time.After(callTimeout):
c.Err = fmt.Errorf("BLE call timeout")
}
}
return c
}
func (obj *blob) call(method string, args ...interface{}) error {
return obj.callv(method, args...).Err
}
// Print prints the object.
func (obj *blob) Print(w io.Writer) {
fmt.Fprintf(w, "%s [%s]\n", obj.path, obj.iface)
printProperties(w, "", obj.properties)
}
func printProperties(w io.Writer, iface string, props Properties) {
indent := " "
if iface != "" {
fmt.Fprintf(w, "%s%s\n", indent, iface)
indent += indent
}
for key, val := range props {
s := val.String()
switch key {
case "UUID":
u, ok := val.Value().(string)
if ok {
s = ShortUUID(u)
}
case "UUIDs":
uuids, ok := val.Value().([]string)
if ok {
s = UUIDs(uuids).String()
}
}
fmt.Fprintf(w, "%s%s %s\n", indent, key, s)
}
}
// The findObject function tests each object with functions of type predicate.
type predicate func(*blob) bool
// findObject finds an object satisfying the given predicate.
// If returns an error if zero or more than one is found.
func (conn *Connection) findObject(iface string, matching predicate) (*blob, error) {
var found []*blob
conn.iterObjects(func(path dbus.ObjectPath, dict Object) bool {
props := dict[iface]
if props == nil {
return false
}
obj := &blob{
conn: conn,
path: path,
iface: iface,
properties: props,
object: conn.bus.Object("org.bluez", path),
}
if matching(obj) {
found = append(found, obj)
}
return false
})
switch len(found) {
case 1:
return found[0], nil
case 0:
return nil, fmt.Errorf("cannot find %s", iface)
default:
return nil, fmt.Errorf("found %d instances of %s", len(found), iface)
}
}
func dot(a, b string) string {
return a + "." + b
}