forked from evcc-io/evcc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sma.go
204 lines (163 loc) Β· 5.31 KB
/
sma.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
package meter
import (
"errors"
"fmt"
"os"
"sort"
"text/tabwriter"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/provider/sma"
"github.com/evcc-io/evcc/util"
"gitlab.com/bboehmke/sunny"
)
// SMA supporting SMA Home Manager 2.0, SMA Energy Meter 30 and SMA inverter
type SMA struct {
uri string
scale float64
device *sma.Device
}
func init() {
registry.Add("sma", NewSMAFromConfig)
}
//go:generate go run ../cmd/tools/decorate.go -f decorateSMA -b *SMA -r api.Meter -t "api.Battery,Soc,func() (float64, error)" -t "api.BatteryCapacity,Capacity,func() float64"
// NewSMAFromConfig creates an SMA meter from generic config
func NewSMAFromConfig(other map[string]interface{}) (api.Meter, error) {
cc := struct {
capacity `mapstructure:",squash"`
URI, Password, Interface string
Serial uint32
Scale float64 // power only
}{
Password: "0000",
Scale: 1,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return NewSMA(cc.URI, cc.Password, cc.Interface, cc.Serial, cc.Scale, cc.capacity.Decorator())
}
// NewSMA creates an SMA meter
func NewSMA(uri, password, iface string, serial uint32, scale float64, capacity func() float64) (api.Meter, error) {
sm := &SMA{
uri: uri,
scale: scale,
}
discoverer, err := sma.GetDiscoverer(iface)
if err != nil {
return nil, fmt.Errorf("discoverer: %w", err)
}
switch {
case uri != "":
sm.device, err = discoverer.DeviceByIP(uri, password)
if err != nil {
return nil, err
}
case serial > 0:
sm.device = discoverer.DeviceBySerial(serial, password)
if sm.device == nil {
return nil, fmt.Errorf("device not found: %d", serial)
}
default:
return nil, errors.New("missing uri or serial")
}
// call UpdateValues first to check if we get an error
if err := sm.device.UpdateValues(); err != nil {
return nil, err
}
// start update loop manually to get values as fast as possible
go sm.device.Run()
// decorate api.Battery in case of inverter
var soc func() (float64, error)
if !sm.device.IsEnergyMeter() {
vals, err := sm.device.Values()
if err != nil {
return nil, err
}
if _, ok := vals[sunny.BatteryCharge]; ok {
soc = sm.soc
}
}
return decorateSMA(sm, soc, capacity), nil
}
// CurrentPower implements the api.Meter interface
func (sm *SMA) CurrentPower() (float64, error) {
values, err := sm.device.Values()
return sm.scale * (sma.AsFloat(values[sunny.ActivePowerPlus]) - sma.AsFloat(values[sunny.ActivePowerMinus])), err
}
var _ api.MeterEnergy = (*SMA)(nil)
// TotalEnergy implements the api.MeterEnergy interface
func (sm *SMA) TotalEnergy() (float64, error) {
values, err := sm.device.Values()
return sma.AsFloat(values[sunny.ActiveEnergyPlus]) / 3600000, err
}
var _ api.PhaseCurrents = (*SMA)(nil)
// Currents implements the api.PhaseCurrents interface
func (sm *SMA) Currents() (float64, float64, float64, error) {
values, err := sm.device.Values()
var powers [3]float64
for i, id := range []sunny.ValueID{sunny.ActivePowerMinusL1, sunny.ActivePowerMinusL2, sunny.ActivePowerMinusL3} {
if p := sma.AsFloat(values[id]); p > 0 {
powers[i] = -p
}
}
var res [3]float64
for i, id := range []sunny.ValueID{sunny.CurrentL1, sunny.CurrentL2, sunny.CurrentL3} {
res[i] = util.SignFromPower(sma.AsFloat(values[id]), powers[i])
}
return res[0], res[1], res[2], err
}
var _ api.PhaseVoltages = (*SMA)(nil)
// Voltages implements the api.PhaseVoltages interface
func (sm *SMA) Voltages() (float64, float64, float64, error) {
values, err := sm.device.Values()
var res [3]float64
for i, id := range []sunny.ValueID{sunny.VoltageL1, sunny.VoltageL2, sunny.VoltageL3} {
res[i] = sma.AsFloat(values[id])
}
return res[0], res[1], res[2], err
}
var _ api.PhasePowers = (*SMA)(nil)
// Powers implements the api.PhasePowers interface
func (sm *SMA) Powers() (float64, float64, float64, error) {
values, err := sm.device.Values()
var res [3]float64
for i, id := range []sunny.ValueID{sunny.ActivePowerPlusL1, sunny.ActivePowerPlusL2, sunny.ActivePowerPlusL3} {
res[i] = sma.AsFloat(values[id])
}
for i, id := range []sunny.ValueID{sunny.ActivePowerMinusL1, sunny.ActivePowerMinusL2, sunny.ActivePowerMinusL3} {
res[i] -= sma.AsFloat(values[id])
}
return res[0], res[1], res[2], err
}
// soc implements the api.Battery interface
func (sm *SMA) soc() (float64, error) {
values, err := sm.device.Values()
return sma.AsFloat(values[sunny.BatteryCharge]), err
}
var _ api.Diagnosis = (*SMA)(nil)
// Diagnose implements the api.Diagnosis interface
func (sm *SMA) Diagnose() {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
fmt.Fprintf(w, " IP:\t%s\n", sm.device.Address())
fmt.Fprintf(w, " Serial:\t%d\n", sm.device.SerialNumber())
fmt.Fprintf(w, " EnergyMeter:\t%v\n", sm.device.IsEnergyMeter())
fmt.Fprintln(w)
if values, err := sm.device.Values(); err == nil {
ids := make([]sunny.ValueID, 0, len(values))
for k := range values {
ids = append(ids, k)
}
sort.Slice(ids, func(i, j int) bool {
return ids[i].String() < ids[j].String()
})
for _, id := range ids {
switch values[id].(type) {
case float64:
fmt.Fprintf(w, " %s:\t%f %s\n", id.String(), values[id], sunny.GetValueInfo(id).Unit)
default:
fmt.Fprintf(w, " %s:\t%v %s\n", id.String(), values[id], sunny.GetValueInfo(id).Unit)
}
}
}
w.Flush()
}