forked from volkszaehler/mbmd
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrs485.go
More file actions
156 lines (126 loc) · 3.78 KB
/
Copy pathrs485.go
File metadata and controls
156 lines (126 loc) · 3.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package rs485
import (
"fmt"
"time"
"github.com/grid-x/modbus"
"github.com/pkg/errors"
"github.com/volkszaehler/mbmd/meters"
)
const (
ReadHoldingReg = 3
ReadInputReg = 4
)
type rs485 struct {
producer Producer
ops chan Operation
inflight Operation
}
// NewDevice creates a device who's type must exist in the producer registry
func NewDevice(typeid string) (meters.Device, error) {
if factory, ok := Producers[typeid]; ok {
device := &rs485{
producer: factory(),
ops: make(chan Operation),
}
// ringbuffer of device operations
go func(d *rs485) {
for {
for _, op := range d.producer.Produce() {
d.ops <- op
}
}
}(device)
return device, nil
}
return nil, fmt.Errorf("unknown meter type %s", typeid)
}
// Initialize prepares the device for usage. Any setup or initilization should be done here.
func (d *rs485) Initialize(client modbus.Client) error {
return nil
}
// Descriptor returns the device descriptor. Since this method doe not have bus access the descriptor should be preared
// during initilization.
func (d *rs485) Descriptor() meters.DeviceDescriptor {
return meters.DeviceDescriptor{
Manufacturer: d.producer.Type(),
Model: d.producer.Description(),
}
}
func (d *rs485) rawQuery(client modbus.Client, op Operation) (bytes []byte, err error) {
if op.ReadLen == 0 {
return bytes, fmt.Errorf("invalid meter operation %v", op)
}
switch op.FuncCode {
case ReadHoldingReg:
bytes, err = client.ReadHoldingRegisters(op.OpCode, op.ReadLen)
case ReadInputReg:
bytes, err = client.ReadInputRegisters(op.OpCode, op.ReadLen)
default:
return bytes, fmt.Errorf("unknown function code %d", op.FuncCode)
}
if err != nil {
return bytes, errors.Wrap(err, "read failed")
}
return bytes, nil
}
func (d *rs485) query(client modbus.Client, op Operation) (res meters.MeasurementResult, err error) {
if op.Transform == nil {
return res, fmt.Errorf("transformation not defined: %v", op)
}
bytes, err := d.rawQuery(client, op)
if err != nil {
return res, err
}
res = meters.MeasurementResult{
Measurement: op.IEC61850,
Value: op.Transform(bytes),
Timestamp: time.Now(),
}
return res, nil
}
// Probe is called by the handler after preparing the bus by setting the device id
func (d *rs485) Probe(client modbus.Client) (res bool, err error) {
op := d.producer.Probe()
// use specific identificator for devices that are able to recognize
// themselves reliably
if idf, ok := d.producer.(Identificator); ok {
bytes, err := d.rawQuery(client, op)
if err != nil {
return false, err
}
match := idf.Identify(bytes)
return match, nil
}
// use default validator looking for 110/230V
measurement, err := d.query(client, op)
if err != nil {
return false, err
}
v := validator{[]float64{110, 230}}
match := v.validate(measurement.Value)
return match, nil
}
// Query is called by the handler after preparing the bus by setting the device id and waiting for rate limit
func (d *rs485) Query(client modbus.Client) (res []meters.MeasurementResult, err error) {
res = make([]meters.MeasurementResult, 0)
// Query loop will try to read all operations in a single run. It will
// always start with the current inflight operation. If an error is encountered,
// the partial results are returned. The loop is terminated after as many
// operations have been executed as the producer provides in a single run.
// In case of a flakey connection this guarantees that all registers are
// read at an equal rate.
for range d.producer.Produce() {
// get next inflight
if d.inflight.FuncCode == 0 {
d.inflight = <-d.ops
}
m, err := d.query(client, d.inflight)
if err != nil {
return res, err
}
// mark inflight operation as completed
d.inflight.FuncCode = 0
res = append(res, m)
}
return res, nil
}