forked from evcc-io/evcc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdiscover.go
156 lines (127 loc) Β· 3.3 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package sma
import (
"context"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/evcc-io/evcc/util"
"gitlab.com/bboehmke/sunny"
)
const udpTimeout = 10 * time.Second
// map of created discover instances
var (
discoverers = make(map[string]*Discoverer)
discoverersMutex sync.Mutex
)
// initialize sunny logger only once
var once sync.Once
// GetDiscoverer fo the given interface
func GetDiscoverer(iface string) (*Discoverer, error) {
// on time initialization of sunny logger
log := util.NewLogger("sma")
once.Do(func() {
sunny.Log = log.TRACE
})
discoverersMutex.Lock()
defer discoverersMutex.Unlock()
// get or create discoverer
discoverer, ok := discoverers[iface]
if !ok {
conn, err := sunny.NewConnection(iface)
if err != nil {
return nil, fmt.Errorf("connection failed: %w", err)
}
discoverer = &Discoverer{
log: log,
conn: conn,
devices: make(map[uint32]*Device),
}
go discoverer.run()
discoverers[iface] = discoverer
}
return discoverer, nil
}
// Discoverer discovers SMA devicesBySerial in background while providing already found devicesBySerial
type Discoverer struct {
log *util.Logger
conn *sunny.Connection
devices map[uint32]*Device
mux sync.RWMutex
done uint32
}
func (d *Discoverer) createDevice(device *sunny.Device) *Device {
return &Device{
Device: device,
log: d.log,
values: util.NewMonitor[map[sunny.ValueID]any](udpTimeout),
}
}
func (d *Discoverer) addDevice(device *sunny.Device) {
d.mux.Lock()
defer d.mux.Unlock()
if _, ok := d.devices[device.SerialNumber()]; !ok {
d.devices[device.SerialNumber()] = d.createDevice(device)
} else {
device.Close()
}
}
// run discover and store found devicesBySerial
func (d *Discoverer) run() {
devices := make(chan *sunny.Device)
go func() {
for device := range devices {
d.addDevice(device)
}
}()
// discover devicesBySerial and wait for results
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
d.conn.DiscoverDevices(ctx, devices, "")
close(devices)
// mark discover as done
atomic.AddUint32(&d.done, 1)
}
func (d *Discoverer) get(serial uint32, password string) *Device {
d.mux.RLock()
defer d.mux.RUnlock()
device := d.devices[serial]
if device != nil {
device.SetPassword(password)
}
return device
}
// DeviceBySerial with the given serial number
func (d *Discoverer) DeviceBySerial(serial uint32, password string) *Device {
start := time.Now()
for time.Since(start) < time.Second*3 {
// discover done -> return immediately regardless of result
if atomic.LoadUint32(&d.done) != 0 {
return d.get(serial, password)
}
// device with serial found -> return
if device := d.get(serial, password); device != nil {
return device
}
time.Sleep(time.Millisecond * 10)
}
return d.get(serial, password)
}
// DeviceByIP with the given serial number
func (d *Discoverer) DeviceByIP(ip, password string) (*Device, error) {
d.mux.Lock()
defer d.mux.Unlock()
for _, device := range d.devices {
if device.Address().IP.String() == ip {
device.SetPassword(password)
return device, nil
}
}
device, err := d.conn.NewDevice(ip, password)
if err != nil {
return nil, fmt.Errorf("failed to get device: %w", err)
}
dev := d.createDevice(device)
d.devices[device.SerialNumber()] = dev
return dev, err
}