Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions cmd/confighandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type InfluxConfig struct {
// AdapterConfig describes device communication parameters
type AdapterConfig struct {
Device string
RTU bool
Baudrate int
Comset string
}
Expand Down Expand Up @@ -104,12 +105,18 @@ func NewDeviceConfigHandler() *DeviceConfigHandler {
}

// createConnection parses adapter string to create TCP or RTU connection
func createConnection(device string, baudrate int, comset string) (res meters.Connection) {
func createConnection(device string, rtu bool, baudrate int, comset string) (res meters.Connection) {
if device == "mock" {
res = meters.NewMock(device) // mocked connection
} else if tcp, _ := regexp.MatchString(":[0-9]+$", device); tcp {
log.Printf("config: creating TCP connection for %s", device)
res = meters.NewTCP(device) // tcp connection
if rtu {
// special case: RTU over TCP
log.Printf("config: creating RTU over TCP connection for %s", device)
res = meters.NewRTUOverTCP(device) // tcp connection
} else {
log.Printf("config: creating TCP connection for %s", device)
res = meters.NewTCP(device) // tcp connection
}
} else {
log.Printf("config: creating RTU connection for %s (%dbaud, %s)", device, baudrate, comset)
if baudrate == 0 || comset == "" {
Expand All @@ -123,23 +130,10 @@ func createConnection(device string, baudrate int, comset string) (res meters.Co
return res
}

// CreateAdapter creates a connection handler for given adapter configuration.
// While connectionManager does the same it is not able to configure the connection.
func (conf *DeviceConfigHandler) CreateAdapter(connSpec string, baudrate int, comset string) meters.Manager {
manager, ok := conf.Managers[connSpec]
if !ok {
conn := createConnection(connSpec, baudrate, comset)
manager = meters.NewManager(conn)
conf.Managers[connSpec] = manager
}

return manager
}

func (conf *DeviceConfigHandler) connectionManager(connSpec string) meters.Manager {
func (conf *DeviceConfigHandler) ConnectionManager(connSpec string, rtu bool, baudrate int, comset string) meters.Manager {
manager, ok := conf.Managers[connSpec]
if !ok {
conn := createConnection(connSpec, 0, "")
conn := createConnection(connSpec, rtu, baudrate, comset)
manager = meters.NewManager(conn)
conf.Managers[connSpec] = manager
}
Expand Down Expand Up @@ -177,6 +171,12 @@ func (conf *DeviceConfigHandler) createDeviceForManager(
return meter
}

// isRTUDevice checks if type is a known RTU device type
func (conf *DeviceConfigHandler) isRTUDevice(meterType string) bool {
_, ok := rs485.Producers[strings.ToUpper(meterType)]
return ok
}

// CreateDevice creates new device and adds it to the connection manager
func (conf *DeviceConfigHandler) CreateDevice(devConf DeviceConfig) {
if devConf.Adapter == "" {
Expand All @@ -191,7 +191,10 @@ func (conf *DeviceConfigHandler) CreateDevice(devConf DeviceConfig) {
}
}

manager := conf.connectionManager(devConf.Adapter)
manager, ok := conf.Managers[devConf.Adapter]
if !ok {
log.Fatalf("Missing adapter configuration for device %v", devConf)
}
meter := conf.createDeviceForManager(manager, devConf.Type)

if err := manager.Add(devConf.ID, meter); err != nil {
Expand All @@ -217,7 +220,6 @@ func (conf *DeviceConfigHandler) CreateDeviceFromSpec(deviceDef string) {
log.Fatalf("Cannot parse connect string- missing physical device or connection for %s. See -h for help.", deviceDef)
}

manager := conf.connectionManager(connSpec)
meterSplit := strings.Split(meterDef, ":")
if len(meterSplit) != 2 {
log.Fatalf("Cannot parse device definition: %s. See -h for help.", meterDef)
Expand All @@ -233,6 +235,11 @@ func (conf *DeviceConfigHandler) CreateDeviceFromSpec(deviceDef string) {
log.Fatalf("Error parsing device id %s: %v. See -h for help.", devID, err)
}

// RTU flag is inferred if the device is of RTU type.
// It is used to implicitly create an RTUOverTCP instead of a TCP connection
rtu := conf.isRTUDevice(meterType)
manager := conf.ConnectionManager(connSpec, rtu, 0, "")

meter := conf.createDeviceForManager(manager, meterType)
if err := manager.Add(uint8(id), meter); err != nil {
log.Fatalf("Error adding device %s: %v. See -h for help.", meterDef, err)
Expand Down
6 changes: 5 additions & 1 deletion cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,17 @@ func printModel(m *smdx.ModelElement) {
}

func inspect(cmd *cobra.Command, args []string) {
if len(args) > 0 {
log.Fatalf("excess arguments, aborting: %v", args)
}

confHandler := NewDeviceConfigHandler()

// create default adapter from configuration
defaultDevice := viper.GetString("adapter")
if defaultDevice != "" {
confHandler.DefaultDevice = defaultDevice
confHandler.CreateAdapter(defaultDevice, viper.GetInt("baudrate"), viper.GetString("comset"))
confHandler.ConnectionManager(defaultDevice, viper.GetBool("rtu"), viper.GetInt("baudrate"), viper.GetString("comset"))
}

// create devices from command line
Expand Down
7 changes: 7 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ The default adapter can be overridden per device`,
"8N1",
`Communication parameters for default adapter, either 8N1 or 8E1.
Only applicable if the default adapter is an RTU device`,
)
rootCmd.PersistentFlags().Bool(
"rtu",
false,
`Use RTU over TCP for default adapter.
Typically used with RS485 to Ethernet adapters that don't perform protocol conversion (e.g. USR-TCP232).
Only applicable if the default adapter is a TCP connection`,
)
rootCmd.PersistentFlags().BoolP(
"help", "h",
Expand Down
7 changes: 5 additions & 2 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ func checkVersion() {

func run(cmd *cobra.Command, args []string) {
log.Printf("mbmd %s (%s)", server.Version, server.Commit)
if len(args) > 0 {
log.Fatalf("excess arguments, aborting: %v", args)
}
go checkVersion()

confHandler := NewDeviceConfigHandler()
Expand All @@ -189,7 +192,7 @@ func run(cmd *cobra.Command, args []string) {
defaultDevice := viper.GetString("adapter")
if defaultDevice != "" {
confHandler.DefaultDevice = defaultDevice
confHandler.CreateAdapter(defaultDevice, viper.GetInt("baudrate"), viper.GetString("comset"))
confHandler.ConnectionManager(defaultDevice, viper.GetBool("rtu"), viper.GetInt("baudrate"), viper.GetString("comset"))
}

// create devices from command line
Expand All @@ -213,7 +216,7 @@ func run(cmd *cobra.Command, args []string) {
if len(devices) == 0 {
// add adapters from configuration
for _, a := range conf.Adapters {
confHandler.CreateAdapter(a.Device, a.Baudrate, a.Comset)
confHandler.ConnectionManager(a.Device, a.RTU, a.Baudrate, a.Comset)
}

// add devices from configuration
Expand Down
8 changes: 6 additions & 2 deletions cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,17 @@ func (v validator) check(f float64) bool {
}

func scan(cmd *cobra.Command, args []string) {
if len(args) > 0 {
log.Fatalf("excess arguments, aborting: %v", args)
}

// create connection
adapter := viper.GetString("adapter")
if adapter == "" {
log.Fatal("Missing adapter configuration")
log.Fatal("missing adapter configuration")
}

conn := createConnection(adapter, viper.GetInt("baudrate"), viper.GetString("comset"))
conn := createConnection(adapter, viper.GetBool("rtu"), viper.GetInt("baudrate"), viper.GetString("comset"))
client := conn.ModbusClient()

// raw log
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ require (
gopkg.in/yaml.v2 v2.2.7 // indirect
)

replace github.com/grid-x/modbus v0.0.0-20191105145357-867898f52408 => github.com/andig/gridx-modbus v0.0.0-20191223181555-ff0a69a1ad97

go 1.13
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andig/gosunspec v0.0.0-20191120114235-802a74abaa2d h1:IX1Jqj7owJlsaDtsxPlRjI/JdHQJ8BIM/rXL2j1ILyA=
github.com/andig/gosunspec v0.0.0-20191120114235-802a74abaa2d/go.mod h1:YkshK8WMzYn1iXAZzHUO75gIqhMSan2ctgBVtBkRIyA=
github.com/andig/gridx-modbus v0.0.0-20191223181555-ff0a69a1ad97 h1:3jXowxWJbymtgs7uDmsPTy2rsOr4IvpOLQ4rog6nAaQ=
github.com/andig/gridx-modbus v0.0.0-20191223181555-ff0a69a1ad97/go.mod h1:9mi0a7/tbYsUWwsC32q/R0r2p1lkNt+8li81Zaf/yTE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
Expand Down
9 changes: 8 additions & 1 deletion mbmd.dist.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@ adapters:
- device: /dev/ttyUSB0
baudrate: 9600
comset: 8N1 # "8E1" needs be quoted as string or will error
- device: 192.168.0.7:23
rtu: true # Modbus RS485 to Ethernet converter uses RTU over TCP

# list of devices
devices:
- type: sdm
- name: sdm1
type: sdm
id: 1
adapter: /dev/ttyUSB0
- name: sdm2
type: sdm
id: 1
adapter: 192.168.0.7:23
- name: sma1
type: sunspec
id: 126
Expand Down
73 changes: 73 additions & 0 deletions meters/rtuovertcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package meters

import (
"time"

"github.com/grid-x/modbus"
)

// RTUOverTCP is an RTU encoder over a TCP modbus connection
type RTUOverTCP struct {
address string
Client modbus.Client
Handler *modbus.RTUOverTCPClientHandler
}

// NewRTUOverTCPClientHandler creates a TCP modbus handler
func NewRTUOverTCPClientHandler(device string) *modbus.RTUOverTCPClientHandler {
handler := modbus.NewRTUOverTCPClientHandler(device)

// set default timings
handler.Timeout = 1 * time.Second
handler.ProtocolRecoveryTimeout = 10 * time.Second // not used
handler.LinkRecoveryTimeout = 15 * time.Second // not used

return handler
}

// NewRTUOverTCP creates a TCP modbus client
func NewRTUOverTCP(address string) Connection {
handler := NewRTUOverTCPClientHandler(address)
client := modbus.NewClient(handler)

b := &RTUOverTCP{
address: address,
Client: client,
Handler: handler,
}

return b
}

// String returns the bus connection address (TCP)
func (b *RTUOverTCP) String() string {
return b.address
}

// ModbusClient returns the TCP modbus client
func (b *RTUOverTCP) ModbusClient() modbus.Client {
return b.Client
}

// Logger sets a logging instance for physical bus operations
func (b *RTUOverTCP) Logger(l Logger) {
b.Handler.Logger = l
}

// Slave sets the modbus device id for the following operations
func (b *RTUOverTCP) Slave(deviceID uint8) {
b.Handler.SetSlave(deviceID)
}

// Timeout sets the modbus timeout
func (b *RTUOverTCP) Timeout(timeout time.Duration) time.Duration {
t := b.Handler.Timeout
b.Handler.Timeout = timeout
return t
}

// Close closes the modbus connection.
// This forces the modbus client to reopen the connection before the next bus operations.
func (b *RTUOverTCP) Close() {
b.Handler.Close()
}