From c48b9b902c5a854441d311dbadd6ab565fd3684c Mon Sep 17 00:00:00 2001 From: andig Date: Fri, 1 May 2020 19:52:20 +0200 Subject: [PATCH] Make SMA energy readings configurable (#84) * Make SMA energy readings configurable * Add decorator --- README.md | 10 +++++ core/wrapper/chargerater_test.go | 2 +- meter/meter.go | 6 +-- meter/sma.go | 63 +++++++++++++++++++++++++++----- meter/sma/listener.go | 18 +++++---- 5 files changed, 77 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 51cdc18f0..f936e8e12 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,16 @@ Meters provide data about power and energy consumption. Available meter implemen energy: Export ``` +- `sma`: SMA Home Manager and SMA Energy Meter. Power reading is configured out of the box but can be customizied if necessary. To obtain energy readings define the desired Obis code (Import Energy: "1:1.8.0", Export Energy: "1:2.8.0"): + + ```yaml + - name: sma-home-manager + type: sdm + uri: 192.168.1.4 + power: # leave empty for mixed import/export energy + energy: # leave empty to disable or chose obis 1:1.8.0/1:2.8.0 + ``` + - `default`: default meter implementation where meter readings- `power` and `energy` are configured using [plugin](#plugins) ### Vehicle diff --git a/core/wrapper/chargerater_test.go b/core/wrapper/chargerater_test.go index a79194a19..fb0b3f477 100644 --- a/core/wrapper/chargerater_test.go +++ b/core/wrapper/chargerater_test.go @@ -44,7 +44,7 @@ func TestWrappedMeter(t *testing.T) { mm := mock.NewMockMeter(ctrl) me := mock.NewMockMeterEnergy(ctrl) - cm := &meter.WrappedMeterEnergy{Meter: mm, MeterEnergy: me} + cm := &meter.MeterEnergyDecorator{Meter: mm, MeterEnergy: me} me.EXPECT(). TotalEnergy(). diff --git a/meter/meter.go b/meter/meter.go index f6f10f361..6c833fe58 100644 --- a/meter/meter.go +++ b/meter/meter.go @@ -6,8 +6,8 @@ import ( "github.com/andig/evcc/util" ) -// WrappedMeterEnergy decorates a Meter with MeterEnergy -type WrappedMeterEnergy struct { +// MeterEnergyDecorator decorates an api.Meter with api.MeterEnergy +type MeterEnergyDecorator struct { api.Meter api.MeterEnergy } @@ -24,7 +24,7 @@ func NewConfigurableFromConfig(log *util.Logger, other map[string]interface{}) a // decorate Meter with MeterEnergy if cc.Energy != nil { - m = &WrappedMeterEnergy{ + m = &MeterEnergyDecorator{ Meter: m, MeterEnergy: NewMeterEnergy(provider.NewFloatGetterFromConfig(log, *cc.Energy)), } diff --git a/meter/sma.go b/meter/sma.go index 304160edc..17277c2ae 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -20,6 +20,9 @@ type SMA struct { log *util.Logger uri string power float64 + energy float64 + powerO sma.Obis + energyO sma.Obis updated time.Time recv chan sma.Telegram mux sync.Mutex @@ -28,22 +31,24 @@ type SMA struct { // NewSMAFromConfig creates a SMA Meter from generic config func NewSMAFromConfig(log *util.Logger, other map[string]interface{}) api.Meter { - sm := struct { - URI string + cc := struct { + URI, Power, Energy string }{} - util.DecodeOther(log, other, &sm) + util.DecodeOther(log, other, &cc) - return NewSMA(sm.URI) + return NewSMA(cc.URI, cc.Power, cc.Energy) } // NewSMA creates a SMA Meter -func NewSMA(uri string) *SMA { +func NewSMA(uri, power, energy string) api.Meter { log := util.NewLogger("sma ") sm := &SMA{ - log: log, - uri: uri, - recv: make(chan sma.Telegram), + log: log, + uri: uri, + powerO: sma.Obis(power), + energyO: sma.Obis(energy), + recv: make(chan sma.Telegram), } if sma.Instance == nil { @@ -54,6 +59,11 @@ func NewSMA(uri string) *SMA { go sm.receive() + // decorate api.MeterEnergy + if power != "" { + return &SMAEnergy{SMA: sm} + } + return sm } @@ -83,16 +93,31 @@ func (sm *SMA) receive() { sm.mux.Lock() - if power, ok := msg.Values[sma.ObisExportPower]; ok { + if sm.powerO != "" { + // use user-defined obis + if power, ok := msg.Values[sm.powerO]; ok { + sm.power = power + sm.updated = time.Now() + } + } else if power, ok := msg.Values[sma.ExportPower]; ok { sm.power = -power sm.updated = time.Now() - } else if power, ok := msg.Values[sma.ObisImportPower]; ok { + } else if power, ok := msg.Values[sma.ImportPower]; ok { sm.power = power sm.updated = time.Now() } else { sm.log.WARN.Println("missing obis for import/export power") } + if sm.energyO != "" { + if energy, ok := msg.Values[sm.energyO]; ok { + sm.energy = energy + sm.updated = time.Now() + } else { + sm.log.WARN.Println("missing obis for energy") + } + } + sm.mux.Unlock() } } @@ -109,3 +134,21 @@ func (sm *SMA) CurrentPower() (float64, error) { return sm.power, nil } + +// SMAEnergy decorates SMA with api.MeterEnergy interface +type SMAEnergy struct { + *SMA +} + +// TotalEnergy implements the api.MeterEnergy interface +func (sm *SMA) TotalEnergy() (float64, error) { + sm.once.Do(sm.waitForInitialValue) + sm.mux.Lock() + defer sm.mux.Unlock() + + if time.Since(sm.updated) > udpTimeout { + return 0, errors.New("recv timeout") + } + + return sm.energy, nil +} diff --git a/meter/sma/listener.go b/meter/sma/listener.go index e63c12ec8..f0c68a831 100644 --- a/meter/sma/listener.go +++ b/meter/sma/listener.go @@ -11,6 +11,8 @@ import ( "github.com/andig/evcc/util" ) +type Obis = string + const ( multicastAddr = "239.12.255.254:9522" udpBufferSize = 8192 @@ -19,10 +21,10 @@ const ( msgPreamble = 28 // preamble size in bytes msgCodeLength = 4 // length in bytes - ObisImportPower = "1:1.4.0" // Wirkleistung (W) - ObisImportEnergy = "1:1.8.0" // Wirkarbeit (Ws) + - ObisExportPower = "1:2.4.0" // Wirkleistung (W) - ObisExportEnergy = "1:2.8.0" // Wirkarbeit (Ws) − + ImportPower Obis = "1:1.4.0" // Wirkleistung (W) + ExportPower Obis = "1:2.4.0" // Wirkleistung (W) + ImportEnergy Obis = "1:1.8.0" // Wirkarbeit (Ws) + + ExportEnergy Obis = "1:2.8.0" // Wirkarbeit (Ws) − ) // obisDefinition defines the properties needed to parse the SMA multicast telegram values @@ -32,10 +34,10 @@ type obisDefinition struct { } // list of Obis codes and their properties as defined in the SMA EMETER-Protokoll-TI-de-10.pdf document -var knownObisCodes = map[string]obisDefinition{ +var knownObisCodes = map[Obis]obisDefinition{ // Overall sums - ObisImportPower: {4, 0.1}, ObisImportEnergy: {8, 1}, // Wirkleistung (W)/-arbeit (Ws) + - ObisExportPower: {4, 0.1}, ObisExportEnergy: {8, 1}, // Wirkleistung (W)/-arbeit (Ws) − + ImportPower: {4, 0.1}, ImportEnergy: {8, 1}, // Wirkleistung (W)/-arbeit (Ws) + + ExportPower: {4, 0.1}, ExportEnergy: {8, 1}, // Wirkleistung (W)/-arbeit (Ws) − "1:3.4.0": {4, 0.1}, "1:3.8.0": {8, 1}, // Blindleistung (W)/-arbeit (Ws) + "1:4.4.0": {4, 0.1}, "1:4.8.0": {8, 1}, // Blindleistung (W)/-arbeit (Ws) − "1:9.4.0": {4, 0.1}, "1:9.8.0": {8, 1}, // Scheinleistung (W)/-arbeit (Ws) + @@ -79,7 +81,7 @@ var Instance *Listener type Telegram struct { Addr string Serial string - Values map[string]float64 + Values map[Obis]float64 } // Listener for receiving SMA multicast data packages