evcc-io/provider/modbus.go
2020-04-21 11:23:29 +02:00

145 lines
3.4 KiB
Go

package provider
import (
"math"
"strings"
"github.com/andig/evcc/api"
"github.com/volkszaehler/mbmd/meters"
"github.com/volkszaehler/mbmd/meters/rs485"
"github.com/volkszaehler/mbmd/meters/sunspec"
)
// Modbus implements modbus RTU and TCP access
type Modbus struct {
log *api.Logger
conn meters.Connection
device meters.Device
slaveID uint8
op rs485.Operation
}
var connections map[string]meters.Connection
func modbusConnection(key string, newConn meters.Connection) meters.Connection {
if connections == nil {
connections = make(map[string]meters.Connection)
}
if conn, ok := connections[key]; ok {
return conn
}
connections[key] = newConn
return newConn
}
// NewModbusFromConfig creates Modbus plugin
func NewModbusFromConfig(log *api.Logger, typ string, other map[string]interface{}) *Modbus {
cc := struct {
URI, Device, Comset string
Meter, Value string
Baudrate int
ID uint8
}{}
api.DecodeOther(log, other, &cc)
var conn meters.Connection
var device meters.Device
var err error
switch strings.ToLower(typ) {
case "modbus-rtu":
conn = modbusConnection(cc.Device, meters.NewRTU(cc.Device, cc.Baudrate, cc.Comset))
device, err = rs485.NewDevice(strings.ToUpper(cc.Meter))
case "modbus-rtuovertcp", "modbus-tcprtu", "modbus-rtutcp":
conn = modbusConnection(cc.URI, meters.NewRTUOverTCP(cc.URI))
device, err = rs485.NewDevice(strings.ToUpper(cc.Meter))
case "modbus-tcp":
conn = modbusConnection(cc.URI, meters.NewTCP(cc.URI))
device = sunspec.NewDevice(strings.ToUpper(cc.Meter))
default:
log.FATAL.Fatalf("invalid provider type %s", typ)
}
log = api.NewLogger("modb")
conn.Logger(log.TRACE)
// prepare device
if err == nil {
conn.Slave(cc.ID)
err = device.Initialize(conn.ModbusClient())
// silence Kostal implementation errors
if _, partial := err.(meters.SunSpecPartiallyInitialized); partial {
err = nil
}
}
if err != nil {
log.FATAL.Fatal(err)
}
measurement := meters.UnStringMeasurement(cc.Value)
if measurement == 0 {
log.FATAL.Fatalf("invalid measurement %s", cc.Value)
}
// for RS485 check if producer supports the measurement
op := rs485.Operation{IEC61850: measurement}
if dev, ok := device.(*rs485.RS485); ok {
op = rs485FindOp(dev, measurement)
if op.IEC61850 == 0 {
log.FATAL.Fatalf("invalid value %s", measurement)
}
}
return &Modbus{
log: log,
conn: conn,
device: device,
op: op,
slaveID: cc.ID,
}
}
func rs485FindOp(device *rs485.RS485, measurement meters.Measurement) (op rs485.Operation) {
ops := device.Producer().Produce()
for _, o := range ops {
if o.IEC61850 == measurement {
op = o
break
}
}
return op
}
// FloatGetter executes configured modbus read operation and implements provider.FloatGetter
func (m *Modbus) FloatGetter() (float64, error) {
m.conn.Slave(m.slaveID)
var res meters.MeasurementResult
var err error
if dev, ok := m.device.(*rs485.RS485); ok {
res, err = dev.QueryOp(m.conn.ModbusClient(), m.op)
}
if dev, ok := m.device.(*sunspec.SunSpec); ok {
res, err = dev.QueryOp(m.conn.ModbusClient(), m.op.IEC61850)
}
if err == nil {
m.log.TRACE.Printf("%+v", res)
}
return res.Value, err
}
// IntGetter executes configured modbus read operation and implements provider.IntGetter
func (m *Modbus) IntGetter() (int64, error) {
res, err := m.FloatGetter()
return int64(math.Round(res)), err
}