@@ -31,6 +31,19 @@ type (
3131 lock sync.RWMutex
3232 staticHosts []DiscoveryTarget
3333 }
34+
35+ serviceDiscoveryTarget struct {
36+ mdns.ServiceEntry
37+
38+ Name string
39+ Host string
40+ Address string
41+ Port int
42+
43+ InfoFields map [string ]string
44+ Generation string
45+ Version string
46+ }
3447)
3548
3649var (
@@ -72,98 +85,186 @@ func (d *serviceDiscovery) init(shellyplugs []string, shellyplus []string, shell
7285}
7386
7487func (d * serviceDiscovery ) Run (timeout time.Duration ) {
88+ var targetList []DiscoveryTarget
89+ targetList = append (targetList , d .staticHosts ... )
90+
7591 wg := sync.WaitGroup {}
76- // Make a channel for results and start listening
77- entriesCh := make (chan * mdns. ServiceEntry , 15 )
92+
93+ targetChannel := make (chan * DiscoveryTarget , 1 )
7894
7995 wg .Add (1 )
8096 go func () {
8197 defer wg .Done ()
82- var targetList []DiscoveryTarget
83- targetList = append (targetList , d .staticHosts ... )
84- for entry := range entriesCh {
85- switch {
86- case strings .HasPrefix (strings .ToLower (entry .Name ), "shellyplug-" ):
87- d .logger .With (
88- slog .Group (
89- "target" ,
90- slog .String ("name" , entry .Name ),
91- slog .String ("address" , entry .AddrV4 .String ()),
92- ),
93- ).Debug (`found target via mDNS servicediscovery` )
94- targetList = append (targetList , DiscoveryTarget {
95- Hostname : entry .Name ,
96- Port : entry .Port ,
97- Address : entry .AddrV4 .String (),
98- Type : TargetTypeShellyPlug ,
99- Static : false ,
100- })
101- case strings .HasPrefix (strings .ToLower (entry .Name ), "shellyplus" ):
102- d .logger .With (
103- slog .Group (
104- "target" ,
105- slog .String ("name" , entry .Name ),
106- slog .String ("address" , entry .AddrV4 .String ()),
107- ),
108- ).Debug (`found target via mDNS servicediscovery` )
109- targetList = append (targetList , DiscoveryTarget {
110- Hostname : entry .Name ,
111- Port : entry .Port ,
112- Address : entry .AddrV4 .String (),
113- Type : TargetTypeShellyPlus ,
114- Static : false ,
115- })
116- case strings .HasPrefix (strings .ToLower (entry .Name ), "shellypro" ):
117- d .logger .With (
118- slog .Group (
119- "target" ,
120- slog .String ("name" , entry .Name ),
121- slog .String ("address" , entry .AddrV4 .String ()),
122- ),
123- ).Debug (`found target via mDNS servicediscovery` )
124- targetList = append (targetList , DiscoveryTarget {
125- Hostname : entry .Name ,
126- Port : entry .Port ,
127- Address : entry .AddrV4 .String (),
128- Type : TargetTypeShellyPro ,
129- Static : false ,
130- })
98+ for target := range targetChannel {
99+ targetList = append (targetList , * target )
100+ }
101+ }()
102+
103+ // mDNS discovery via _http._tcp.
104+ d .discover ("_http._tcp" , timeout , func (logger * slogger.Logger , target * serviceDiscoveryTarget ) * DiscoveryTarget {
105+ switch {
106+ case strings .HasPrefix (target .Name , "shellyplug-" ):
107+ logger .Debug (`found target via mDNS servicediscovery` )
108+ return & DiscoveryTarget {
109+ Hostname : target .Name ,
110+ Port : target .Port ,
111+ Address : target .Address ,
112+ Type : TargetTypeShellyPlug ,
113+ Generation : target .Generation ,
114+ Static : false ,
115+ }
116+ case strings .HasPrefix (target .Name , "shellyplus" ):
117+ logger .Debug (`found target via mDNS servicediscovery` )
118+ return & DiscoveryTarget {
119+ Hostname : target .Name ,
120+ Port : target .Port ,
121+ Address : target .Address ,
122+ Type : TargetTypeShellyPlus ,
123+ Generation : target .Generation ,
124+ Static : false ,
125+ }
126+ case strings .HasPrefix (target .Name , "shellypro" ):
127+ logger .Debug (`found target via mDNS servicediscovery` )
128+ return & DiscoveryTarget {
129+ Hostname : target .Name ,
130+ Port : target .Port ,
131+ Address : target .Address ,
132+ Type : TargetTypeShellyPro ,
133+ Generation : target .Generation ,
134+ Static : false ,
131135 }
132136 }
133- d .logger .Debug (`finished mDNS servicediscovery"` , slog .Int ("targets" , len (targetList )))
134-
135- d .lock .Lock ()
136- defer d .lock .Unlock ()
137-
138- // reduce all non-discovered targets health
139- for target := range d .targetList {
140- // set to low health if target was healthy before
141- // reduce health even further if not detected
142- // some devices seems not to respond to mdns discovery after some time,
143- // so try to keep them alive here
144- if d .targetList [target ].Health > TargetHealthLow {
145- d .targetList [target ].Health = TargetHealthLow
146- } else {
147- // reduce health even more for each failed service discovery
148- d .targetList [target ].Health = d .targetList [target ].Health - 1
137+
138+ if gen , ok := target .InfoFields ["gen" ]; ok {
139+ switch strings .ToLower (gen ) {
140+ case "2" :
141+ return & DiscoveryTarget {
142+ Hostname : target .Name ,
143+ Port : target .Port ,
144+ Address : target .Address ,
145+ Type : TargetTypeShellyPro ,
146+ Generation : target .Generation ,
147+ Static : false ,
148+ }
149149 }
150150 }
151151
152- // set all discovered targets to good health
153- for _ , row := range targetList {
154- target := row
155- d .targetList [target .Address ] = & target
156- d .targetList [target .Address ].Health = TargetHealthGood
152+ return nil
153+ }, targetChannel )
154+
155+ // mDNS discovery via _shelly._tcp
156+ d .discover ("_shelly._tcp" , timeout , func (logger * slogger.Logger , target * serviceDiscoveryTarget ) * DiscoveryTarget {
157+ if gen , ok := target .InfoFields ["gen" ]; ok {
158+ switch strings .ToLower (gen ) {
159+ case "2" :
160+ return & DiscoveryTarget {
161+ Hostname : target .Name ,
162+ Port : target .Port ,
163+ Address : target .Address ,
164+ Type : TargetTypeShellyPro ,
165+ Generation : target .Generation ,
166+ Static : false ,
167+ }
168+ }
169+ }
170+
171+ return nil
172+ }, targetChannel )
173+
174+ close (targetChannel )
175+ wg .Wait ()
176+
177+ d .lock .Lock ()
178+ defer d .lock .Unlock ()
179+
180+ // reduce all non-discovered targets health
181+ for target := range d .targetList {
182+ // set to low health if target was healthy before
183+ // reduce health even further if not detected
184+ // some devices seems not to respond to mdns discovery after some time,
185+ // so try to keep them alive here
186+ if d .targetList [target ].Health > TargetHealthLow {
187+ d .targetList [target ].Health = TargetHealthLow
188+ } else {
189+ // reduce health even more for each failed service discovery
190+ d .targetList [target ].Health = d .targetList [target ].Health - 1
191+ }
192+ }
193+
194+ // set all discovered targets to good health
195+ for _ , row := range targetList {
196+ target := row
197+ d .targetList [target .Address ] = & target
198+ d .targetList [target .Address ].Health = TargetHealthGood
199+ }
200+
201+ d .logger .Debug (`finished mDNS servicediscovery"` , slog .Int ("targets" , len (d .targetList )))
202+
203+ d .cleanup ()
204+ }
205+
206+ func (d * serviceDiscovery ) discover (service string , timeout time.Duration , callback func (logger * slogger.Logger , target * serviceDiscoveryTarget ) * DiscoveryTarget , channel chan * DiscoveryTarget ) {
207+ wg := sync.WaitGroup {}
208+ // Make a channel for results and start listening
209+ entriesCh := make (chan * mdns.ServiceEntry , 30 )
210+
211+ discoveryLogger := d .logger .With (slog .String ("service" , service ))
212+
213+ wg .Add (1 )
214+ go func () {
215+ defer wg .Done ()
216+ for entry := range entriesCh {
217+ target := serviceDiscoveryTarget {
218+ ServiceEntry : * entry ,
219+ Name : strings .ToLower (entry .Name ),
220+ Host : strings .ToLower (entry .Host ),
221+ Port : entry .Port ,
222+ Address : entry .AddrV4 .String (),
223+ InfoFields : map [string ]string {},
224+ Generation : "" ,
225+ Version : "" ,
226+ }
227+
228+ // skip if we dont have a name or address
229+ if target .Name == "" || target .Address == "" {
230+ continue
231+ }
232+
233+ // parse info fields
234+ for _ , field := range entry .InfoFields {
235+ fieldParts := strings .SplitN (field , "=" , 2 )
236+ if len (fieldParts ) == 2 {
237+ target .InfoFields [fieldParts [0 ]] = fieldParts [1 ]
238+ }
239+ }
240+
241+ if val , ok := target .InfoFields ["gen" ]; ok {
242+ target .Generation = val
243+ }
244+
245+ if val , ok := target .InfoFields ["ver" ]; ok {
246+ target .Version = val
247+ }
248+
249+ entryLogger := discoveryLogger .With (
250+ slog .Group (
251+ "target" ,
252+ slog .String ("name" , target .Name ),
253+ slog .String ("address" , target .Address ),
254+ ),
255+ )
256+ if target := callback (entryLogger , & target ); target != nil {
257+ channel <- target
258+ }
157259 }
158- d .cleanup ()
159260 }()
160261
161262 // Start the lookup
162- params := mdns .DefaultParams ("_http._tcp" )
263+ params := mdns .DefaultParams (service )
163264 params .DisableIPv6 = true
164265 params .Timeout = timeout
165266 params .Entries = entriesCh
166- params .Logger = slog .NewLogLogger (d . logger .Handler (), slog .LevelInfo )
267+ params .Logger = slog .NewLogLogger (discoveryLogger .Handler (), slog .LevelInfo )
167268 err := mdns .Query (params )
168269 if err != nil {
169270 panic (err )
@@ -228,7 +329,7 @@ func (d *serviceDiscovery) GetTargetList() []DiscoveryTarget {
228329 return targetList
229330}
230331
231- func discoveryTargetFromStatic (entry string , typ string ) DiscoveryTarget {
332+ func discoveryTargetFromStatic (entry string , deviceType string ) DiscoveryTarget {
232333 parts := strings .Split (entry , ":" )
233334 var name string
234335 var port int
@@ -248,7 +349,7 @@ func discoveryTargetFromStatic(entry string, typ string) DiscoveryTarget {
248349 Hostname : name ,
249350 Port : port ,
250351 Address : name ,
251- Type : typ ,
352+ Type : deviceType ,
252353 Static : true ,
253354 Health : TargetHealthGood ,
254355 }
0 commit comments