volkszaehler / mbmd

ModBus Measurement Daemon - simple reading of data from ModBus meters and grid inverters
BSD 3-Clause "New" or "Revised" License
224 stars 81 forks source link

Add Carlo Gavazzi EM500 #304

Open andig opened 1 year ago

lsbbs commented 3 months ago

Quick and dirty modified from mbmd/meters/rs485/carlogavazzi-ex3x0.go for the most important values. A complete list of Modbus register is available at https://gavazzi.se/app/uploads/2022/03/em500-cp-v1r3-eng.pdf

package rs485

import (
        . "github.com/volkszaehler/mbmd/meters"
)

func init() {
        Register("CGEM500", NewCarloGavazziEM500Producer)
}

type CarloGavazziEM500Producer struct {
        Opcodes
}

func NewCarloGavazziEM500Producer() Producer {

        ops := Opcodes{
                VoltageL1: 0x00,
                VoltageL2: 0x02,
                VoltageL3: 0x04,
                Voltage: 0x24,
                CurrentL1: 0x0C,
                CurrentL2: 0x0E,
                CurrentL3: 0x10,
                PowerL1:   0x12,
                PowerL2:   0x14,
                PowerL3:   0x16,
                ApparentPowerL1:     0x18,
                ApparentPowerL2:     0x1A,
                ApparentPowerL3:     0x1C,
                ReactivePowerL1:     0x1E,
                ReactivePowerL2:     0x20,
                ReactivePowerL3:     0x22,
                Power:     0x28,
                ApparentPower:        0x2A,
                ReactivePower:        0x2C,
                CosphiL1:  0x2C,
                CosphiL2:  0x2E,
                CosphiL3:  0x2F,
                Cosphi:    0x31,
                Frequency: 0x33,
                Import:    0x34,
                ImportL1:  0x40,
                ImportL2:  0x42,
                ImportL3:  0x44,
                Export:    0x4E,
        }
        return &CarloGavazziEM500Producer{Opcodes: ops}
}
// Description implements Producer interface
func (p *CarloGavazziEM500Producer) Description() string {
        return "Carlo Gavazzi EM 530/540"
}

func (p *CarloGavazziEM500Producer) snip16(iec Measurement, scaler ...float64) Operation {
        transform := RTUInt16ToFloat64 // default conversion
        if len(scaler) > 0 {
                transform = MakeScaledTransform(transform, scaler[0])
        }

        operation := Operation{
                FuncCode:  ReadInputReg,
                OpCode:    p.Opcode(iec),
                ReadLen:   1,
                IEC61850:  iec,
                Transform: transform,
        }
        return operation
}

func (p *CarloGavazziEM500Producer) snip32(iec Measurement, scaler ...float64) Operation {
        transform := RTUInt32ToFloat64Swapped // default conversion
        if len(scaler) > 0 {
                transform = MakeScaledTransform(transform, scaler[0])
        }

        operation := Operation{
                FuncCode:  ReadInputReg,
                OpCode:    p.Opcode(iec),
                ReadLen:   2,
                IEC61850:  iec,
                Transform: transform,
        }
        return operation
}

// Probe implements Producer interface
func (p *CarloGavazziEM500Producer) Probe() Operation {
        return p.snip32(VoltageL1, 10)
}

// Produce implements Producer interface
func (p *CarloGavazziEM500Producer) Produce() (res []Operation) {
        for _, op := range []Measurement{
                VoltageL1, VoltageL2, VoltageL3,
                Voltage,
        } {
                res = append(res, p.snip32(op, 10))
        }

        for _, op := range []Measurement{
                CurrentL1, CurrentL2, CurrentL3,
        } {
                res = append(res, p.snip32(op, 1000))
        }

        for _, op := range []Measurement{
                Cosphi, CosphiL1, CosphiL2, CosphiL3,
        } {
                res = append(res, p.snip16(op, 1000))
        }

        for _, op := range []Measurement{
                Frequency,
        } {
                res = append(res, p.snip16(op, 10))
        }

        for _, op := range []Measurement{
                Power, PowerL1, PowerL2, PowerL3,
        } {
                res = append(res, p.snip32(op, 10))
        }

        for _, op := range []Measurement{
                Import, ImportL1, ImportL2, ImportL3,
                Export,
        } {
                res = append(res, p.snip32(op, 10))
        }

        for _, op := range[]Measurement{
                ApparentPowerL1, ApparentPowerL2, ApparentPowerL3,
                ReactivePowerL1, ReactivePowerL2, ReactivePowerL3,
                ApparentPower, ReactivePower,
        } {
                res = append(res, p.snip32(op, 10))
        }

        return res
}

Save it as mbmd/meters/rs485/carlogavazzi-em500.go and compile mbmd new. Configuration somehow like this in mbmd.yaml:

- name: cg
  type: cgem500
  id: 2
  adapter: /dev/ttySC1
andig commented 3 months ago

Happy to take a pr 👍🏻