From 510de6176078a240f17068a041c09c1ead0eabfa Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 23 Mar 2020 12:34:45 +0100 Subject: [PATCH] Differentiate SDM220/230 meters (#74) Co-authored-by: joekokker <59933882+joekokker@users.noreply.github.com> --- assets/index.html | 7 +++ docs/mbmd_inspect.md | 4 +- docs/mbmd_run.md | 4 +- meters/measurement_string.go | 5 ++- meters/measurements.go | 3 ++ meters/rs485/sdm.go | 24 +++++----- meters/rs485/sdm220.go | 64 ++++++++++++++++++++++++++ meters/rs485/sdm230.go | 69 ++++++++++++++++++++++++++++ server/assets.go | 87 ++++++++++++++++++------------------ 9 files changed, 208 insertions(+), 59 deletions(-) create mode 100644 meters/rs485/sdm220.go create mode 100644 meters/rs485/sdm230.go diff --git a/assets/index.html b/assets/index.html index 137959ab..3f87de9c 100644 --- a/assets/index.html +++ b/assets/index.html @@ -358,6 +358,13 @@

Measurements

${ m.THD } — + + Phase Angle + — + — + — + ${ m.PhaseAngle } + Frequency (Hz) — diff --git a/docs/mbmd_inspect.md b/docs/mbmd_inspect.md index 8ef56c9e..6b33c819 100644 --- a/docs/mbmd_inspect.md +++ b/docs/mbmd_inspect.md @@ -25,7 +25,9 @@ mbmd inspect [flags] INEPRO Inepro Metering Pro 380 JANITZA Janitza B-Series meters SBC Saia Burgess Controls ALE3 meters - SDM Eastron SDM meters + SDM Eastron SDM630 + SDM220 Eastron SDM220 + SDM230 Eastron SDM230 TCP SUNS Sunspec-compatible MODBUS TCP device (SMA, SolarEdge, KOSTAL, etc) To use an adapter different from default, append RTU device or TCP address separated by @. diff --git a/docs/mbmd_run.md b/docs/mbmd_run.md index 81b14ad1..cab70bf4 100644 --- a/docs/mbmd_run.md +++ b/docs/mbmd_run.md @@ -24,7 +24,9 @@ mbmd run [flags] INEPRO Inepro Metering Pro 380 JANITZA Janitza B-Series meters SBC Saia Burgess Controls ALE3 meters - SDM Eastron SDM meters + SDM Eastron SDM630 + SDM220 Eastron SDM220 + SDM230 Eastron SDM230 TCP SUNS Sunspec-compatible MODBUS TCP device (SMA, SolarEdge, KOSTAL, etc) To use an adapter different from default, append RTU device or TCP address separated by @. diff --git a/meters/measurement_string.go b/meters/measurement_string.go index 56ae9ba6..6638659a 100644 --- a/meters/measurement_string.go +++ b/meters/measurement_string.go @@ -99,11 +99,12 @@ func _() { _ = x[DCEnergyS3-89] _ = x[ChargeState-90] _ = x[BatteryVoltage-91] + _ = x[PhaseAngle-92] } -const _Measurement_name = "FrequencyCurrentCurrentL1CurrentL2CurrentL3VoltageVoltageL1VoltageL2VoltageL3PowerPowerL1PowerL2PowerL3ImportPowerImportPowerL1ImportPowerL2ImportPowerL3ExportPowerExportPowerL1ExportPowerL2ExportPowerL3ReactivePowerReactivePowerL1ReactivePowerL2ReactivePowerL3ApparentPowerApparentPowerL1ApparentPowerL2ApparentPowerL3CosphiCosphiL1CosphiL2CosphiL3THDTHDL1THDL2THDL3SumSumT1SumT2SumL1SumL2SumL3ImportImportT1ImportT2ImportL1ImportL2ImportL3ExportExportT1ExportT2ExportL1ExportL2ExportL3ReactiveSumReactiveSumT1ReactiveSumT2ReactiveSumL1ReactiveSumL2ReactiveSumL3ReactiveImportReactiveImportT1ReactiveImportT2ReactiveImportL1ReactiveImportL2ReactiveImportL3ReactiveExportReactiveExportT1ReactiveExportT2ReactiveExportL1ReactiveExportL2ReactiveExportL3DCCurrentDCVoltageDCPowerHeatSinkTempDCCurrentS1DCVoltageS1DCPowerS1DCEnergyS1DCCurrentS2DCVoltageS2DCPowerS2DCEnergyS2DCCurrentS3DCVoltageS3DCPowerS3DCEnergyS3ChargeStateBatteryVoltage" +const _Measurement_name = "FrequencyCurrentCurrentL1CurrentL2CurrentL3VoltageVoltageL1VoltageL2VoltageL3PowerPowerL1PowerL2PowerL3ImportPowerImportPowerL1ImportPowerL2ImportPowerL3ExportPowerExportPowerL1ExportPowerL2ExportPowerL3ReactivePowerReactivePowerL1ReactivePowerL2ReactivePowerL3ApparentPowerApparentPowerL1ApparentPowerL2ApparentPowerL3CosphiCosphiL1CosphiL2CosphiL3THDTHDL1THDL2THDL3SumSumT1SumT2SumL1SumL2SumL3ImportImportT1ImportT2ImportL1ImportL2ImportL3ExportExportT1ExportT2ExportL1ExportL2ExportL3ReactiveSumReactiveSumT1ReactiveSumT2ReactiveSumL1ReactiveSumL2ReactiveSumL3ReactiveImportReactiveImportT1ReactiveImportT2ReactiveImportL1ReactiveImportL2ReactiveImportL3ReactiveExportReactiveExportT1ReactiveExportT2ReactiveExportL1ReactiveExportL2ReactiveExportL3DCCurrentDCVoltageDCPowerHeatSinkTempDCCurrentS1DCVoltageS1DCPowerS1DCEnergyS1DCCurrentS2DCVoltageS2DCPowerS2DCEnergyS2DCCurrentS3DCVoltageS3DCPowerS3DCEnergyS3ChargeStateBatteryVoltagePhaseAngle" -var _Measurement_index = [...]uint16{0, 9, 16, 25, 34, 43, 50, 59, 68, 77, 82, 89, 96, 103, 114, 127, 140, 153, 164, 177, 190, 203, 216, 231, 246, 261, 274, 289, 304, 319, 325, 333, 341, 349, 352, 357, 362, 367, 370, 375, 380, 385, 390, 395, 401, 409, 417, 425, 433, 441, 447, 455, 463, 471, 479, 487, 498, 511, 524, 537, 550, 563, 577, 593, 609, 625, 641, 657, 671, 687, 703, 719, 735, 751, 760, 769, 776, 788, 799, 810, 819, 829, 840, 851, 860, 870, 881, 892, 901, 911, 922, 936} +var _Measurement_index = [...]uint16{0, 9, 16, 25, 34, 43, 50, 59, 68, 77, 82, 89, 96, 103, 114, 127, 140, 153, 164, 177, 190, 203, 216, 231, 246, 261, 274, 289, 304, 319, 325, 333, 341, 349, 352, 357, 362, 367, 370, 375, 380, 385, 390, 395, 401, 409, 417, 425, 433, 441, 447, 455, 463, 471, 479, 487, 498, 511, 524, 537, 550, 563, 577, 593, 609, 625, 641, 657, 671, 687, 703, 719, 735, 751, 760, 769, 776, 788, 799, 810, 819, 829, 840, 851, 860, 870, 881, 892, 901, 911, 922, 936, 946} func (i Measurement) String() string { i -= 1 diff --git a/meters/measurements.go b/meters/measurements.go index d385b36a..49d145e5 100644 --- a/meters/measurements.go +++ b/meters/measurements.go @@ -138,6 +138,8 @@ const ( // Battery ChargeState BatteryVoltage + + PhaseAngle ) var iec = map[Measurement][]string{ @@ -232,6 +234,7 @@ var iec = map[Measurement][]string{ DCEnergyS3: {"String 3 Generation", "kWh"}, ChargeState: {"Charge State", "%"}, BatteryVoltage: {"Battery Voltage", "V"}, + PhaseAngle: {"Phase Angle", "°"}, } // MarshalText implements encoding.TextMarshaler diff --git a/meters/rs485/sdm.go b/meters/rs485/sdm.go index 2c284f81..c5b0a913 100644 --- a/meters/rs485/sdm.go +++ b/meters/rs485/sdm.go @@ -16,19 +16,19 @@ type SDMProducer struct { func NewSDMProducer() Producer { /** - * Opcodes as defined by Eastron. + * Opcodes as defined by Eastron SDM630. * See http://bg-etech.de/download/manual/SDM630Register.pdf - * Please note that this is the superset of all SDM devices - - * some opcodes might not work on some devicep. + * This is to a large extent a superset of all SDM devices, however there are + * subtle differences (see 220, 230). Some opcodes might not work on some devices. */ ops := Opcodes{ - VoltageL1: 0x0000, + VoltageL1: 0x0000, // 220, 230 VoltageL2: 0x0002, VoltageL3: 0x0004, - CurrentL1: 0x0006, + CurrentL1: 0x0006, // 220, 230 CurrentL2: 0x0008, CurrentL3: 0x000A, - PowerL1: 0x000C, + PowerL1: 0x000C, // 230 PowerL2: 0x000E, PowerL3: 0x0010, Power: 0x0034, @@ -38,16 +38,16 @@ func NewSDMProducer() Producer { ImportL1: 0x015a, ImportL2: 0x015c, ImportL3: 0x015e, - Import: 0x0048, + Import: 0x0048, // 220, 230 ExportL1: 0x0160, ExportL2: 0x0162, ExportL3: 0x0164, - Export: 0x004a, + Export: 0x004a, // 220, 230 SumL1: 0x0166, SumL2: 0x0168, SumL3: 0x016a, - Sum: 0x0156, - CosphiL1: 0x001e, + Sum: 0x0156, // 220 + CosphiL1: 0x001e, // 230 CosphiL2: 0x0020, CosphiL3: 0x0022, Cosphi: 0x003e, @@ -55,7 +55,7 @@ func NewSDMProducer() Producer { THDL2: 0x00ec, // voltage THDL3: 0x00ee, // voltage THD: 0x00F8, // voltage - Frequency: 0x0046, + Frequency: 0x0046, // 230 //L1THDCurrent: 0x00F0, // current //L2THDCurrent: 0x00F2, // current //L3THDCurrent: 0x00F4, // current @@ -70,7 +70,7 @@ func (p *SDMProducer) Type() string { } func (p *SDMProducer) Description() string { - return "Eastron SDM meters" + return "Eastron SDM630" } func (p *SDMProducer) snip(iec Measurement) Operation { diff --git a/meters/rs485/sdm220.go b/meters/rs485/sdm220.go new file mode 100644 index 00000000..dfc10260 --- /dev/null +++ b/meters/rs485/sdm220.go @@ -0,0 +1,64 @@ +package rs485 + +import . "github.com/volkszaehler/mbmd/meters" + +func init() { + Register(NewSDM220Producer) +} + +const ( + METERTYPE_SDM220 = "SDM220" +) + +type SDM220Producer struct { + Opcodes +} + +func NewSDM220Producer() Producer { + /** + * Opcodes as defined by Eastron SDM220. + * See https://bg-etech.de/download/manual/SDM220StandardDE.pdf + */ + ops := Opcodes{ + Voltage: 0x0000, // 220, 230 + Current: 0x0006, // 220, 230 + Import: 0x0048, // 220, 230 + Export: 0x004a, // 220, 230 + Sum: 0x0156, // 220, 230 + ReactiveSum: 0x0158, // 220 + ReactiveImport: 0x4C, // 220, 230 + ReactiveExport: 0x4E, // 220, 230 + } + return &SDM220Producer{Opcodes: ops} +} + +func (p *SDM220Producer) Type() string { + return METERTYPE_SDM220 +} + +func (p *SDM220Producer) Description() string { + return "Eastron SDM220" +} + +func (p *SDM220Producer) snip(iec Measurement) Operation { + operation := Operation{ + FuncCode: ReadInputReg, + OpCode: p.Opcode(iec), + ReadLen: 2, + IEC61850: iec, + Transform: RTUIeee754ToFloat64, + } + return operation +} + +func (p *SDM220Producer) Probe() Operation { + return p.snip(Voltage) +} + +func (p *SDM220Producer) Produce() (res []Operation) { + for op := range p.Opcodes { + res = append(res, p.snip(op)) + } + + return res +} diff --git a/meters/rs485/sdm230.go b/meters/rs485/sdm230.go new file mode 100644 index 00000000..6c0dabfa --- /dev/null +++ b/meters/rs485/sdm230.go @@ -0,0 +1,69 @@ +package rs485 + +import . "github.com/volkszaehler/mbmd/meters" + +func init() { + Register(NewSDM230Producer) +} + +const ( + METERTYPE_SDM230 = "SDM230" +) + +type SDM230Producer struct { + Opcodes +} + +func NewSDM230Producer() Producer { + /** + * Opcodes as defined by Eastron SDM230. + * See https://bg-etech.de/download/manual/SDM230-register.pdf + */ + ops := Opcodes{ + Voltage: 0x0000, // 220, 230 + Current: 0x0006, // 220, 230 + Power: 0x000C, // 230 + Import: 0x0048, // 220, 230 + Export: 0x004a, // 220, 230 + Cosphi: 0x001e, // 230 + Frequency: 0x0046, // 230 + ReactiveImport: 0x4C, // 220, 230 + ReactiveExport: 0x4E, // 220, 230 + ApparentPower: 0x0012, // 230 + ReactivePower: 0x0018, // 230 + Sum: 0x0156, // 230 + PhaseAngle: 0x0024, // 230 + } + return &SDM230Producer{Opcodes: ops} +} + +func (p *SDM230Producer) Type() string { + return METERTYPE_SDM230 +} + +func (p *SDM230Producer) Description() string { + return "Eastron SDM230" +} + +func (p *SDM230Producer) snip(iec Measurement) Operation { + operation := Operation{ + FuncCode: ReadInputReg, + OpCode: p.Opcode(iec), + ReadLen: 2, + IEC61850: iec, + Transform: RTUIeee754ToFloat64, + } + return operation +} + +func (p *SDM230Producer) Probe() Operation { + return p.snip(Voltage) +} + +func (p *SDM230Producer) Produce() (res []Operation) { + for op := range p.Opcodes { + res = append(res, p.snip(op)) + } + + return res +} diff --git a/server/assets.go b/server/assets.go index b1364d72..39d3c3f3 100644 --- a/server/assets.go +++ b/server/assets.go @@ -586,51 +586,52 @@ JgIA "/index.html": { name: "index.html", local: "../assets/index.html", - size: 19150, + size: 19416, modtime: 1566640112, compressed: ` -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 +s8rAI9hLAAA= `, },