This repository has been archived by the owner on Dec 28, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 14
/
discovery.go
308 lines (268 loc) · 8.34 KB
/
discovery.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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
package lutron
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"github.com/go-home-iot/connection-pool"
"github.com/markdaws/gohome/pkg/feature"
"github.com/markdaws/gohome/pkg/gohome"
errExt "github.com/pkg/errors"
)
var infos = []gohome.DiscovererInfo{gohome.DiscovererInfo{
ID: "lutron.l-bdgpro2-wh",
Name: "Lutron - Caseta Wireless Smart Bridge",
Description: "Discover Lutron Caseta Wireless Smart Bridge devices",
PreScanInfo: "To get your configuration information, go to the Lutron app, then go: " +
"Settings -> Advanced -> Integration -> Send Integration Report. Copy and paste the contents " +
"of the email into the box below. You also need the IP address of the Smart Bridge device, to find " +
"that go to Settings -> Advanced -> Integration -> Network Settings.",
UIFields: []gohome.UIField{
gohome.UIField{
ID: "ipaddress",
Label: "IP Address",
Description: "The IP Address of the Lutron Smart Hub",
Required: true,
},
gohome.UIField{
ID: "integrationreport",
Label: "Integration Report",
Description: "The Integration report for the Smart Home Hub",
Required: true,
},
},
}}
type discovery struct {
System *gohome.System
}
func (d *discovery) Discoverers() []gohome.DiscovererInfo {
// List all of the discoverers we support
return infos
}
func (d *discovery) DiscovererFromID(ID string) gohome.Discoverer {
switch ID {
case "lutron.l-bdgpro2-wh":
return &discoverer{System: d.System, info: infos[0]}
default:
return nil
}
}
type discoverer struct {
System *gohome.System
info gohome.DiscovererInfo
}
func (d *discoverer) Info() gohome.DiscovererInfo {
return d.info
}
func badConfig(err error) error {
return errExt.Wrap(err, "invalid integration report")
}
func (d *discoverer) ScanDevices(sys *gohome.System, uiFields map[string]string) (*gohome.DiscoveryResults, error) {
result := &gohome.DiscoveryResults{}
// We need to know which device is the Smart Bridge Pro - it is always ID==1 in the config file
var smartBridgeProID string = "1"
var configJSON map[string]interface{}
if err := json.Unmarshal([]byte(uiFields["integrationreport"]), &configJSON); err != nil {
return nil, badConfig(err)
}
root, ok := configJSON["LIPIdList"].(map[string]interface{})
if !ok {
return nil, badConfig(errors.New("missing LIPIdList key, or value not a map"))
}
devices, ok := root["Devices"].([]interface{})
if !ok {
return nil, badConfig(errors.New("missing Devices key, or value not a map"))
}
var makeDevice = func(
modelNumber,
name,
address string,
deviceMap map[string]interface{},
hub *gohome.Device,
auth *gohome.Auth) *gohome.Device {
device := gohome.NewDevice(
d.System.NewID(),
name,
"",
modelNumber,
"",
"",
address,
hub,
nil,
nil,
auth)
btns, ok := deviceMap["Buttons"].([]interface{})
if !ok {
//no buttons
return device
}
for _, buttonMap := range btns {
button := buttonMap.(map[string]interface{})
btnNumber := strconv.FormatFloat(button["Number"].(float64), 'f', 0, 64)
var btnName string
if name, ok := button["Name"]; ok {
btnName = name.(string)
} else {
btnName = "Button " + btnNumber
}
btn := feature.NewButton(d.System.NewID())
btn.Name = btnName
btn.Description = ""
btn.Address = btnNumber
btn.DeviceID = device.ID
device.AddFeature(btn)
}
return device
}
var makeScenes = func(deviceMap map[string]interface{}, sbp *gohome.Device) ([]*gohome.Scene, error) {
var scenes = []*gohome.Scene{}
buttons, ok := deviceMap["Buttons"].([]interface{})
if !ok {
// no buttons
return nil, nil
}
for _, buttonMap := range buttons {
button, ok := buttonMap.(map[string]interface{})
if !ok {
return nil, badConfig(errors.New("expected Button elements to be objects"))
}
if name, ok := button["Name"]; ok && !strings.HasPrefix(name.(string), "Button ") {
var buttonID string = strconv.FormatFloat(button["Number"].(float64), 'f', 0, 64)
var buttonName = button["Name"].(string)
_ = buttonName
var btn *feature.Feature
for _, f := range sbp.Features {
if f.Type == feature.FTButton && f.Address == buttonID {
btn = f
break
}
}
if btn == nil {
return nil, badConfig(errors.New("invalid button number"))
}
/*
//TODO: Fix
scene := &gohome.Scene{
ID: d.System.NewID(),
Address: buttonID,
Name: buttonName,
Description: buttonName,
Commands: []cmd.Command{
&cmd.ButtonPress{
ID: d.System.NextGlobalID(),
ButtonAddress: btn.Address,
ButtonID: btn.ID,
DeviceName: sbp.Name,
DeviceAddress: sbp.Address,
DeviceID: sbp.ID,
},
&cmd.ButtonRelease{
ID: d.System.NextGlobalID(),
ButtonAddress: btn.Address,
ButtonID: btn.ID,
DeviceName: sbp.Name,
DeviceAddress: sbp.Address,
DeviceID: sbp.ID,
},
},
}
scenes = append(scenes, scene)
*/
}
}
return scenes, nil
}
// First need to find the Smart Bridge Pro since it is needed to make scenes and zones
var sbp *gohome.Device
for _, deviceMap := range devices {
device, ok := deviceMap.(map[string]interface{})
if !ok {
return nil, badConfig(errors.New("expected Devices elements to be objects"))
}
var deviceID = strconv.FormatFloat(device["ID"].(float64), 'f', 0, 64)
var deviceName string = device["Name"].(string)
if deviceID == smartBridgeProID {
dev := makeDevice(
"l-bdgpro2-wh",
deviceName,
uiFields["ipaddress"],
device,
nil,
&gohome.Auth{
Login: "lutron",
Password: "integration",
})
sbp = dev
//TODO: Needed for serialization?
pool := pool.NewPool(pool.Config{
Name: sbp.Name,
Size: 2,
})
sbp.Connections = pool
// The smart bridge pro controls scenes by having phantom buttons that can be pressed,
// each button activates a different scene. This means it really has two addresses, the
// first is the IP address to talk to it, but then it also have the DeviceID which is needed
// to press the buttons, so here, we make another device and assign the buttons to this
// new device and use the lutron hub solely for communicating to.
sbpSceneDevice := makeDevice("", "Buttons ["+deviceName+"]", deviceID, device, sbp, nil)
scenes, err := makeScenes(device, sbpSceneDevice)
if err != nil {
return nil, err
}
result.Devices = append(result.Devices, sbp, sbpSceneDevice)
result.Scenes = append(result.Scenes, scenes...)
break
}
}
if sbp == nil {
return nil, badConfig(errors.New("did not find Smart Bridge Pro with ID:" + smartBridgeProID))
}
for _, deviceMap := range devices {
device, ok := deviceMap.(map[string]interface{})
if !ok {
return nil, badConfig(errors.New("expected Devices elements to be objects"))
}
// Don't want to re-add the SBP
var deviceID = strconv.FormatFloat(device["ID"].(float64), 'f', 0, 64)
if deviceID == smartBridgeProID {
continue
}
var deviceName string = device["Name"].(string)
gohomeDevice := makeDevice("", deviceName, deviceID, device, sbp, nil)
result.Devices = append(result.Devices, gohomeDevice)
}
zones, ok := root["Zones"].([]interface{})
if !ok {
return nil, badConfig(errors.New("missing Zones key"))
}
for _, zoneMap := range zones {
z := zoneMap.(map[string]interface{})
var zoneID = strconv.FormatFloat(z["ID"].(float64), 'f', 0, 64)
var zoneName = z["Name"].(string)
// Simple heuristic, if the user put the word "shade" in the name we will
// make this a window treatment
if strings.Contains(strings.ToLower(zoneName), "shade") ||
strings.Contains(strings.ToLower(zoneName), "window") {
wt := feature.NewWindowTreatment(d.System.NewID())
wt.Name = zoneName
wt.Address = zoneID
wt.DeviceID = sbp.ID
err := sbp.AddFeature(wt)
if err != nil {
return nil, badConfig(fmt.Errorf("error adding window treatment to device\n", err))
}
} else {
light := feature.NewLightZone(d.System.NewID(), feature.LightZoneModeContinuous)
light.Name = zoneName
light.Address = zoneID
light.DeviceID = sbp.ID
err := sbp.AddFeature(light)
if err != nil {
return nil, badConfig(fmt.Errorf("error adding light zone to device\n", err))
}
}
}
return result, nil
}