forked from savaki/go.hue
-
Notifications
You must be signed in to change notification settings - Fork 5
/
discover.go
107 lines (96 loc) · 2.92 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
package hue
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/stefanwichmann/lanscan"
)
const discoveryTimeout = 3 * time.Second
// DiscoverBridges is a two-step approach trying to find your hue bridges.
// First it will try to discover bridges in your network using UPnP and it
// will utilize the hue api (https://www.meethue.com/api/nupnp) to
// fetch a list of known bridges at the current location in parallel.
// Should this fail it will automatically scan all hosts in your local
// network and identify any bridges you have running.
// If the parameter discoverAllBridges is true the discovery will wait for all
// bridges to respond. When set to false, this method will return as soon as it
// found the first bridge in your network.
func DiscoverBridges(discoverAllBridges bool) ([]Bridge, error) {
hostChannel := make(chan string, 10)
bridgeChannel := make(chan string, 10)
// Start UPnP and N-UPnP discovery in parallel
go upnpDiscover(hostChannel)
go nupnpDiscover(hostChannel)
go validateBridges(hostChannel, bridgeChannel)
var bridges = []Bridge{}
scanStarted := false
loop:
for {
select {
case bridge, more := <-bridgeChannel:
if !more && len(bridges) > 0 {
return bridges, nil
}
if !more {
break loop
}
bridges = append(bridges, *NewBridge(bridge, ""))
if !discoverAllBridges {
return bridges, nil
}
case <-time.After(discoveryTimeout):
if len(bridges) > 0 {
return bridges, nil
}
if !scanStarted {
// UPnP and N-UPnP didn't discover any bridges.
// Start a LAN scan and feed results to hostChannel.
scanLocalNetwork(hostChannel)
scanStarted = true
continue // Loop again with same timeout
}
break loop
}
}
// Nothing found
return bridges, errors.New("Bridge discovery failed")
}
func scanLocalNetwork(hostChannel chan<- string) {
hosts, err := lanscan.ScanLinkLocal("tcp4", 80, 20, discoveryTimeout-1*time.Second)
if err == nil {
for _, host := range hosts {
hostChannel <- host
}
}
close(hostChannel)
}
func validateBridges(candidates <-chan string, bridges chan<- string) {
for candidate := range candidates {
resp, err := http.Get(fmt.Sprintf("http://%s/description.xml", candidate))
if err != nil {
continue
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
continue
}
// make sure it's a hue bridge
str := string(body)
if !strings.Contains(str, "<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>") {
continue
}
if !(strings.Contains(str, "<manufacturer>Royal Philips Electronics</manufacturer>") || strings.Contains(str, "<manufacturer>Signify</manufacturer>")) {
continue
}
if !(strings.Contains(str, "<modelURL>http://www.meethue.com</modelURL>") || strings.Contains(str, "<modelURL>http://www.philips-hue.com</modelURL>")) {
continue
}
// Candidate seems to be a valid hue bridge
bridges <- candidate
}
close(bridges)
}