From 49bf1e6ec60ccb05e32dcd183ae458a2bf536700 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 8 Feb 2024 20:28:01 +0100 Subject: [PATCH] Sunspec: refactor model selection (#11933) --- provider/modbus_sunspec.go | 14 +- .../definition/meter/sunspec-hybrid.yaml | 119 +++++++++++----- .../definition/meter/sunspec-inverter.yaml | 131 +++++++++++------- util/modbus/mbmd.go | 6 +- util/modbus/modbus_test.go | 7 +- util/modbus/sunspec.go | 39 ++---- 6 files changed, 193 insertions(+), 123 deletions(-) diff --git a/provider/modbus_sunspec.go b/provider/modbus_sunspec.go index aa9a5d293..39d7aec11 100644 --- a/provider/modbus_sunspec.go +++ b/provider/modbus_sunspec.go @@ -31,7 +31,7 @@ func init() { func NewModbusSunspecFromConfig(other map[string]interface{}) (Provider, error) { cc := struct { modbus.Settings `mapstructure:",squash"` - Value string + Value []string Scale float64 Delay time.Duration ConnectDelay time.Duration @@ -67,7 +67,7 @@ func NewModbusSunspecFromConfig(other map[string]interface{}) (Provider, error) log := util.NewLogger("sunspec") conn.Logger(log.TRACE) - if cc.Value == "" { + if len(cc.Value) == 0 { return nil, errors.New("value is required") } @@ -77,9 +77,13 @@ func NewModbusSunspecFromConfig(other map[string]interface{}) (Provider, error) return nil, err } - ops, err := modbus.ParsePoint(cc.Value) - if err != nil { - return nil, fmt.Errorf("invalid sunspec value: %s", cc.Value) + var ops []modbus.SunSpecOperation + for _, val := range cc.Value { + op, err := modbus.ParsePoint(val) + if err != nil { + return nil, fmt.Errorf("invalid sunspec value: %s", cc.Value) + } + ops = append(ops, op) } mb := &ModbusSunspec{ diff --git a/templates/definition/meter/sunspec-hybrid.yaml b/templates/definition/meter/sunspec-hybrid.yaml index 72c0fa53e..4c44d1e03 100644 --- a/templates/definition/meter/sunspec-hybrid.yaml +++ b/templates/definition/meter/sunspec-hybrid.yaml @@ -15,59 +15,104 @@ params: advanced: true render: | {{- if eq .usage "grid" }} - type: mbmd - {{- include "modbus" . }} + type: custom # sunspec model 203 (int+sf)/ 213 (float) meter - power: 203|213:W - energy: 203|213:TotWhImp + power: + source: sunspec + {{- include "modbus" . | indent 2 }} + value: + - 203:W + - 213:W + energy: + source: sunspec + {{- include "modbus" . | indent 2 }} + value: + - 203:TotWhImp + - 213:TotWhImp currents: - - 203|213:AphA - - 203|213:AphB - - 203|213:AphC + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:AphA + - 213:AphA + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:AphB + - 213:AphB + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:AphC + - 213:AphC voltages: - - 203|213:PhVphA - - 203|213:PhVphB - - 203|213:PhVphC + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:PhVphA + - 213:PhVphA + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:PhVphB + - 213:PhVphB + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:PhVphC + - 213:PhVphC powers: - - 203|213:WphA - - 203|213:WphB - - 203|213:WphC + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:WphA + - 213:WphA + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:WphB + - 213:WphB + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:WphC + - 213:WphC {{- end }} {{- if eq .usage "pv" }} type: custom power: source: calc add: - - source: sunspec - {{- include "modbus" . | indent 4 }} - value: 160:1:DCW # mppt 1 - - source: sunspec - {{- include "modbus" . | indent 4 }} - value: 160:2:DCW # mppt 2 + - source: sunspec + {{- include "modbus" . | indent 6 }} + value: 160:1:DCW # mppt 1 + - source: sunspec + {{- include "modbus" . | indent 6 }} + value: 160:2:DCW # mppt 2 energy: source: calc add: - - source: sunspec - {{- include "modbus" . | indent 4 }} - value: 160:1:DCWH # mppt 1 - scale: 0.001 - - source: sunspec - {{- include "modbus" . | indent 4 }} - value: 160:2:DCWH # mppt 2 - scale: 0.001 + - source: sunspec + {{- include "modbus" . | indent 6 }} + value: 160:1:DCWH # mppt 1 + scale: 0.001 + - source: sunspec + {{- include "modbus" . | indent 6 }} + value: 160:2:DCWH # mppt 2 + scale: 0.001 {{- end }} {{- if eq .usage "battery" }} type: custom power: source: calc add: - - source: sunspec - {{- include "modbus" . | indent 4 }} - value: 160:3:DCW # mppt 3 (charge) - scale: -1 - - source: sunspec - {{- include "modbus" . | indent 4 }} - value: 160:4:DCW # mppt 4 (discharge) + - source: sunspec + {{- include "modbus" . | indent 6 }} + value: 160:3:DCW # mppt 3 (charge) + scale: -1 + - source: sunspec + {{- include "modbus" . | indent 6 }} + value: 160:4:DCW # mppt 4 (discharge) energy: source: sunspec {{- include "modbus" . | indent 2 }} @@ -76,8 +121,8 @@ render: | soc: source: sunspec {{- include "modbus" . | indent 2 }} - value: 124:ChaState - {{- if .capacity }} + value: + - 124:ChaState + - 802:SoC capacity: {{ .capacity }} # kWh {{- end }} - {{- end }} diff --git a/templates/definition/meter/sunspec-inverter.yaml b/templates/definition/meter/sunspec-inverter.yaml index 031227db1..8a7ac6d15 100644 --- a/templates/definition/meter/sunspec-inverter.yaml +++ b/templates/definition/meter/sunspec-inverter.yaml @@ -9,63 +9,96 @@ params: choice: ["grid", "pv", "battery"] - name: modbus choice: ["tcpip", "rs485"] - - name: integer - description: - de: Integer Registerzugriff (nur für Netzzähler) - en: Integer register access (grid meter only) - help: - de: Einstellung Float/Integer im Wechselrichter überprüfen - en: Verify Float/Integer setting in inverter - advanced: true - type: bool - name: capacity advanced: true render: | - type: mbmd - {{- include "modbus" . }} {{- if eq .usage "grid" }} - model: sunspec - {{- if eq .integer "true" }} - # sunspec model 203 (int+sf) meter - power: 203:W - energy: 203:TotWhImp + type: custom + # sunspec model 203 (int+sf)/ 213 (float) meter + power: + source: sunspec + {{- include "modbus" . | indent 2 }} + value: + - 203:W + - 213:W + energy: + source: sunspec + {{- include "modbus" . | indent 2 }} + value: + - 203:TotWhImp + - 213:TotWhImp currents: - - 203:AphA - - 203:AphB - - 203:AphC + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:AphA + - 213:AphA + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:AphB + - 213:AphB + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:AphC + - 213:AphC voltages: - - 203:PhVphA - - 203:PhVphB - - 203:PhVphC + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:PhVphA + - 213:PhVphA + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:PhVphB + - 213:PhVphB + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:PhVphC + - 213:PhVphC powers: - - 203:WphA - - 203:WphB - - 203:WphC - {{ else }} - # sunspec model 213 (float) meter - power: 213:W - energy: 213:TotWhImp - currents: - - 213:AphA - - 213:AphB - - 213:AphC - voltages: - - 213:PhVphA - - 213:PhVphB - - 213:PhVphC - powers: - - 213:WphA - - 213:WphB - - 213:WphC + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:WphA + - 213:WphA + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:WphB + - 213:WphB + - source: sunspec + {{- include "modbus" . | indent 4 }} + value: + - 203:WphC + - 213:WphC + {{- end }} + {{- if eq .usage "pv" }} + type: custom + power: + source: sunspec + {{- include "modbus" . | indent 2 }} + value: + - 101:W + - 103:W + energy: + source: sunspec + {{- include "modbus" . | indent 2 }} + value: + - 101:WH + - 103:WH + scale: 0.001 {{- end }} - {{- else }} - model: sunspec - power: Power - energy: Export {{- if eq .usage "battery" }} - soc: 124:ChaState - {{- if .capacity }} + type: custom + soc: + source: sunspec + {{- include "modbus" . | indent 2 }} + value: + - 124:ChaState + - 802:SoC capacity: {{ .capacity }} # kWh {{- end }} - {{- end }} - {{- end }} diff --git a/util/modbus/mbmd.go b/util/modbus/mbmd.go index 7ceeece81..ec9693eb8 100644 --- a/util/modbus/mbmd.go +++ b/util/modbus/mbmd.go @@ -21,11 +21,9 @@ func ParseOperation(dev meters.Device, measurement string) (Operation, error) { // if measurement cannot be parsed it could be SunSpec model/block/point op.MBMD.IEC61850, err = meters.MeasurementString(measurement) if err != nil { - suns, err := ParsePoint(measurement) - if err == nil { - op.SunSpec = suns[0] + if op.SunSpec, err = ParsePoint(measurement); err != nil { + return op, err } - return op, err } // for RS485 check if producer supports the measurement diff --git a/util/modbus/modbus_test.go b/util/modbus/modbus_test.go index bf87c71ec..70a18082e 100644 --- a/util/modbus/modbus_test.go +++ b/util/modbus/modbus_test.go @@ -9,11 +9,10 @@ import ( func TestParsePoint(t *testing.T) { tc := []struct { in string - ops []SunSpecOperation + ops SunSpecOperation }{ - {"103:W", []SunSpecOperation{{103, 0, "W"}}}, - {"802:1:V", []SunSpecOperation{{802, 1, "V"}}}, - {"101|103:DCW", []SunSpecOperation{{101, 0, "DCW"}, {103, 0, "DCW"}}}, + {"103:W", SunSpecOperation{103, 0, "W"}}, + {"802:1:V", SunSpecOperation{802, 1, "V"}}, } for _, tc := range tc { diff --git a/util/modbus/sunspec.go b/util/modbus/sunspec.go index 363ef73ca..8b8629107 100644 --- a/util/modbus/sunspec.go +++ b/util/modbus/sunspec.go @@ -13,39 +13,30 @@ type SunSpecOperation struct { } // ParsePoint parses sunspec point from string -func ParsePoint(selector string) ([]SunSpecOperation, error) { +func ParsePoint(selector string) (SunSpecOperation, error) { + var ( + res SunSpecOperation + err error + ) + el := strings.Split(selector, ":") if len(el) < 2 || len(el) > 3 { - return nil, fmt.Errorf("invalid sunspec format: %s", selector) + return res, fmt.Errorf("invalid sunspec format: %s", selector) } - models := strings.Split(el[0], "|") - if len(models) == 0 { - return nil, fmt.Errorf("missing sunspec model: %s", selector) + if res.Model, err = strconv.Atoi(el[0]); err != nil { + return res, fmt.Errorf("invalid sunspec model: %s", selector) } - var res []SunSpecOperation - for _, m := range models { - model, err := strconv.Atoi(m) + if len(el) == 3 { + // block is the middle element + res.Block, err = strconv.Atoi(el[1]) if err != nil { - return nil, fmt.Errorf("invalid sunspec model: %s", selector) + return res, fmt.Errorf("invalid sunspec block: %s", selector) } - - var block int - if len(el) == 3 { - // block is the middle element - block, err = strconv.Atoi(el[1]) - if err != nil { - return nil, fmt.Errorf("invalid sunspec block: %s", selector) - } - } - - res = append(res, SunSpecOperation{ - Model: model, - Block: block, - Point: el[len(el)-1], - }) } + res.Point = el[len(el)-1] + return res, nil }