Skip to content

Commit 1228158

Browse files
authored
Allow accessing subdevices by specifying subdevice id as slave.subdevice (volkszaehler#138)
1 parent 693925b commit 1228158

File tree

16 files changed

+247
-202
lines changed

16 files changed

+247
-202
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,16 @@ are supported, too. SunSpec defines a default register layout for accessing
290290
the devices.
291291

292292
Supported inverters include popular devices from SolarEdge (SE3000, SE9000)
293-
and SMA (Sunny Boy and Sunny Tripower).
293+
and SMA (Sunny Boy and Sunny TriPower).
294294

295-
In case of TCP connection, the adapter paramter becomes the hostname and port:
295+
In case of TCP connection, the adapter parameter becomes the hostname and port:
296296

297297
./mbmd run -a 192.168.0.44:502 -d SMA:23
298298

299+
SunSpec devices can host multiple subdevices, e.g. to expose a meter attached to an inverter. To access a subdevice, append its id to the slave id:
300+
301+
./mbmd run -a 192.168.0.44:502 -d FRONIUS:1.0 -d FRONIUS:1.1
302+
299303

300304
# Changelog
301305

assets/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
<div id="realtime">
4242
<h1>Measurements</h1>
4343
<p>${ message }</p>
44-
<table class="metertable table table-striped" v-for="(m, idx) in sortedMeters">
44+
<table class="metertable table table-striped" v-for="(m, idx) in sorted(meters)">
4545
<thead class="thead-dark">
4646
<tr class="d-flex">
4747
<th class="col-3">${ idx }</th>
@@ -394,7 +394,7 @@ <h1>Status</h1>
394394
</tr>
395395
</thead>
396396
<tbody>
397-
<tr v-for="(m, idx) in meters">
397+
<tr v-for="(m, idx) in sorted(meters)">
398398
<td>${ idx }</td>
399399
<td>${ m.Type }</td>
400400
<td>${ m.Status }</td>

assets/js/app.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1+
let sort = {
2+
methods: {
3+
sorted: function(theMap) {
4+
var devs = Object.keys(theMap);
5+
devs.sort();
6+
var res = {};
7+
devs.forEach(function (key) {
8+
res[key] = theMap[key];
9+
});
10+
return res;
11+
}
12+
}
13+
}
14+
115
var dataapp = new Vue({
216
el: '#realtime',
317
delimiters: ['${', '}'],
18+
mixins: [sort],
419
data: {
520
meters: {},
621
message: 'Loading...'
722
},
8-
computed: {
9-
// return meters sorted by name
10-
sortedMeters: function() {
11-
var devs = Object.keys(this.meters);
12-
devs.sort();
13-
var res = {};
14-
devs.forEach(function(key) {
15-
res[key] = this.meters[key];
16-
}, this);
17-
return res;
18-
}
19-
},
2023
methods: {
21-
// pop returns true if it was called with any non-null argumnt
24+
// pop returns true if it was called with any non-null argument
2225
pop: function () {
2326
for(var i=0; i<arguments.length; i++) {
2427
if (arguments[i] !== undefined && arguments[i] !== null && arguments[i] !== "") {
@@ -48,6 +51,7 @@ var timeapp = new Vue({
4851
var statusapp = new Vue({
4952
el: '#status',
5053
delimiters: ['${', '}'],
54+
mixins: [sort],
5155
data: {
5256
meters: {}
5357
}
@@ -131,11 +135,9 @@ function connectSocket() {
131135
ws = new WebSocket(protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + "/ws");
132136

133137
ws.onerror = function(evt) {
134-
// console.warn("Connection error");
135138
ws.close();
136139
}
137140
ws.onclose = function (evt) {
138-
// console.warn("Connection closed");
139141
window.setTimeout(connectSocket, 100);
140142
};
141143
ws.onmessage = function (evt) {

cmd/commons.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,20 @@ func meterHelp() string {
6161
}
6262

6363
// countDevices counts all devices for all meters
64-
func countDevices(managers map[string]meters.Manager) int {
64+
func countDevices(managers map[string]*meters.Manager) int {
6565
var count int
6666
for _, m := range managers {
6767
m.All(func(id uint8, dev meters.Device) {
68-
log.Printf("config: declared device %s:%d", dev.Descriptor().Manufacturer, id)
68+
conf := dev.Descriptor()
69+
log.Printf("config: declared device %s:%d.%d", conf.Type, id, conf.SubDevice)
6970
count++
7071
})
7172
}
7273
return count
7374
}
7475

7576
// setLogger enabled raw logging for all devices
76-
func setLogger(managers map[string]meters.Manager, logger meters.Logger) {
77+
func setLogger(managers map[string]*meters.Manager, logger meters.Logger) {
7778
for _, m := range managers {
7879
m.Conn.Logger(logger)
7980
}

cmd/confighandler.go

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,22 +83,23 @@ type AdapterConfig struct {
8383

8484
// DeviceConfig describes a single device's configuration
8585
type DeviceConfig struct {
86-
Type string
87-
ID uint8
88-
Name string
89-
Adapter string
86+
Type string
87+
ID uint8
88+
SubDevice int
89+
Name string
90+
Adapter string
9091
}
9192

9293
// DeviceConfigHandler creates map of meter managers from given configuration
9394
type DeviceConfigHandler struct {
9495
DefaultDevice string
95-
Managers map[string]meters.Manager
96+
Managers map[string]*meters.Manager
9697
}
9798

9899
// NewDeviceConfigHandler creates a configuration handler
99100
func NewDeviceConfigHandler() *DeviceConfigHandler {
100101
conf := &DeviceConfigHandler{
101-
Managers: make(map[string]meters.Manager),
102+
Managers: make(map[string]*meters.Manager),
102103
}
103104
return conf
104105
}
@@ -130,7 +131,7 @@ func createConnection(device string, rtu bool, baudrate int, comset string) (res
130131
}
131132

132133
// ConnectionManager returns connection manager from cache or creates new connection wrapped by manager
133-
func (conf *DeviceConfigHandler) ConnectionManager(connSpec string, rtu bool, baudrate int, comset string) meters.Manager {
134+
func (conf *DeviceConfigHandler) ConnectionManager(connSpec string, rtu bool, baudrate int, comset string) *meters.Manager {
134135
manager, ok := conf.Managers[connSpec]
135136
if !ok {
136137
conn := createConnection(connSpec, rtu, baudrate, comset)
@@ -142,8 +143,9 @@ func (conf *DeviceConfigHandler) ConnectionManager(connSpec string, rtu bool, ba
142143
}
143144

144145
func (conf *DeviceConfigHandler) createDeviceForManager(
145-
manager meters.Manager,
146+
manager *meters.Manager,
146147
meterType string,
148+
subdevice int,
147149
) meters.Device {
148150
var meter meters.Device
149151
meterType = strings.ToUpper(meterType)
@@ -159,8 +161,12 @@ func (conf *DeviceConfigHandler) createDeviceForManager(
159161

160162
sort.SearchStrings(sunspecTypes, meterType)
161163
if isSunspec {
162-
meter = sunspec.NewDevice(meterType)
164+
meter = sunspec.NewDevice(meterType, subdevice)
163165
} else {
166+
if subdevice > 0 {
167+
log.Fatalf("Invalid subdevice number for device %s: %d", meterType, subdevice)
168+
}
169+
164170
var err error
165171
meter, err = rs485.NewDevice(meterType)
166172
if err != nil {
@@ -189,7 +195,7 @@ func (conf *DeviceConfigHandler) CreateDevice(devConf DeviceConfig) {
189195
if !ok {
190196
log.Fatalf("Missing adapter configuration for device %v", devConf)
191197
}
192-
meter := conf.createDeviceForManager(manager, devConf.Type)
198+
meter := conf.createDeviceForManager(manager, devConf.Type, devConf.SubDevice)
193199

194200
if err := manager.Add(devConf.ID, meter); err != nil {
195201
log.Fatalf("Error adding device %v: %v.", devConf, err)
@@ -224,7 +230,19 @@ func (conf *DeviceConfigHandler) CreateDeviceFromSpec(deviceDef string) {
224230
log.Fatalf("Cannot parse device definition- meter type empty: %s. See -h for help.", meterDef)
225231
}
226232

227-
id, err := strconv.Atoi(devID)
233+
var subdevice int
234+
devIDSplit := strings.SplitN(devID, ".", 2)
235+
if len(devIDSplit) == 2 {
236+
var err error
237+
subdevice, err = strconv.Atoi(devIDSplit[1])
238+
if err != nil {
239+
log.Fatalf("Error parsing device id %s: %v. See -h for help.", devID, err)
240+
}
241+
} else if len(devIDSplit) > 2 {
242+
log.Fatalf("Error parsing device id %s. See -h for help.", devID)
243+
}
244+
245+
id, err := strconv.Atoi(devIDSplit[0])
228246
if err != nil {
229247
log.Fatalf("Error parsing device id %s: %v. See -h for help.", devID, err)
230248
}
@@ -233,7 +251,7 @@ func (conf *DeviceConfigHandler) CreateDeviceFromSpec(deviceDef string) {
233251
// have been created of the --rtu flag was specified. We'll not re-check this here.
234252
manager := conf.ConnectionManager(connSpec, false, 0, "")
235253

236-
meter := conf.createDeviceForManager(manager, meterType)
254+
meter := conf.createDeviceForManager(manager, meterType, subdevice)
237255
if err := manager.Add(uint8(id), meter); err != nil {
238256
log.Fatalf("Error adding device %s: %v. See -h for help.", meterDef, err)
239257
}

cmd/scan.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"errors"
45
"fmt"
56
golog "log"
67
"os"
@@ -97,15 +98,15 @@ func scan(cmd *cobra.Command, args []string) {
9798
v := validator{[]float64{110, 230}}
9899

99100
SCAN:
100-
// loop over all valid slave adresses
101+
// loop over all valid slave addresses
101102
for deviceID := 1; deviceID <= 247; deviceID++ {
102103
// give the bus some time to recover before querying the next device
103104
time.Sleep(40 * time.Millisecond)
104105
conn.Slave(uint8(deviceID))
105106

106107
for _, dev := range devices {
107108
if err := dev.Initialize(client); err != nil {
108-
if _, partial := err.(meters.SunSpecPartiallyInitialized); !partial {
109+
if !errors.Is(err, meters.ErrPartiallyOpened) {
109110
continue // devices
110111
}
111112
log.Println(err) // log error but continue

mbmd.dist.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ devices:
4040
- name: sma1
4141
type: sunspec
4242
id: 126
43+
subdevice: 0 # use subdevice to access SunSpec subdevices
4344
adapter: 192.168.0.40:502

meters/device.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@ import (
44
"github.com/grid-x/modbus"
55
)
66

7-
// SunSpecPartiallyInitialized indicates error during device initialization.
8-
// The sunspec device's model tree may be incomplete.
9-
type SunSpecPartiallyInitialized interface {
10-
PartiallyInitialized()
11-
}
12-
137
// DeviceDescriptor describes a device
148
type DeviceDescriptor struct {
9+
Type string
1510
Manufacturer string
1611
Model string
1712
Options string
1813
Version string
1914
Serial string
15+
SubDevice int
2016
}
2117

2218
// Device is a modbus device that can be described, probed and queried

meters/errors.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@ package meters
22

33
import "errors"
44

5-
// ErrNaN indicates a NaN reading result
6-
var ErrNaN = errors.New("NaN value")
5+
var (
6+
// ErrNaN indicates a NaN reading result
7+
ErrNaN = errors.New("NaN value")
8+
9+
// ErrPartiallyOpened indicates a partially opened device
10+
ErrPartiallyOpened = errors.New("Device partially opened")
11+
)

meters/manager.go

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,33 @@
11
package meters
22

3-
import (
4-
"fmt"
5-
)
3+
type device struct {
4+
id uint8
5+
dev Device
6+
}
67

78
// Manager handles devices attached to a connection
89
type Manager struct {
9-
devices map[uint8]Device
10+
devices []device
1011
Conn Connection
1112
}
1213

1314
// NewManager creates a new connection manager instance. connection managers operate devices on a connection instance
14-
func NewManager(conn Connection) Manager {
15+
func NewManager(conn Connection) *Manager {
1516
m := Manager{
16-
devices: make(map[uint8]Device, 1),
17+
devices: make([]device, 0),
1718
Conn: conn,
1819
}
19-
return m
20+
return &m
2021
}
2122

2223
// Add adds device to the device manager at specified device id
23-
func (m *Manager) Add(id uint8, device Device) error {
24-
if _, ok := m.devices[id]; ok {
25-
return fmt.Errorf("duplicate device id %d", id)
24+
func (m *Manager) Add(id uint8, dev Device) error {
25+
device := device{
26+
id: id,
27+
dev: dev,
2628
}
2729

28-
m.devices[id] = device
30+
m.devices = append(m.devices, device)
2931
return nil
3032
}
3133

@@ -35,9 +37,18 @@ func (m *Manager) Count() int {
3537
}
3638

3739
// All iterates over all devices and executes the callback per device.
38-
// Before the callback, the slave id is set on the underlying connection if access is true.
3940
func (m *Manager) All(cb func(uint8, Device)) {
40-
for id, device := range m.devices {
41-
cb(id, device)
41+
for _, device := range m.devices {
42+
cb(device.id, device.dev)
43+
}
44+
}
45+
46+
// Find iterates over devices and executes the callback per device until true is returned.
47+
func (m *Manager) Find(cb func(uint8, Device) bool) bool {
48+
for _, device := range m.devices {
49+
if cb(device.id, device.dev) {
50+
return true
51+
}
4252
}
53+
return false
4354
}

0 commit comments

Comments
 (0)