Skip to content

Commit 6539a8f

Browse files
authored
Add capability for executing individual queries per device (#112)
Adds QueryOp method and exposes RS485 and SunSpec device types
1 parent 612973d commit 6539a8f

File tree

2 files changed

+96
-53
lines changed

2 files changed

+96
-53
lines changed

meters/rs485/rs485.go

+43-34
Original file line numberDiff line numberDiff line change
@@ -14,50 +14,58 @@ const (
1414
ReadInputReg = 4
1515
)
1616

17-
type rs485 struct {
17+
// RS485 implements meters.Device
18+
type RS485 struct {
1819
producer Producer
1920
ops chan Operation
2021
inflight Operation
2122
}
2223

2324
// NewDevice creates a device who's type must exist in the producer registry
24-
func NewDevice(typeid string) (meters.Device, error) {
25+
func NewDevice(typeid string) (*RS485, error) {
2526
if factory, ok := Producers[typeid]; ok {
26-
device := &rs485{
27+
device := &RS485{
2728
producer: factory(),
28-
ops: make(chan Operation),
2929
}
30-
31-
// ringbuffer of device operations
32-
go func(d *rs485) {
33-
for {
34-
for _, op := range d.producer.Produce() {
35-
d.ops <- op
36-
}
37-
}
38-
}(device)
39-
4030
return device, nil
4131
}
4232

4333
return nil, fmt.Errorf("unknown meter type %s", typeid)
4434
}
4535

46-
// Initialize prepares the device for usage. Any setup or initilization should be done here.
47-
func (d *rs485) Initialize(client modbus.Client) error {
36+
// Initialize prepares the device for usage. Any setup or initialization should be done here.
37+
func (d *RS485) Initialize(client modbus.Client) error {
4838
return nil
4939
}
5040

51-
// Descriptor returns the device descriptor. Since this method doe not have bus access the descriptor should be preared
52-
// during initilization.
53-
func (d *rs485) Descriptor() meters.DeviceDescriptor {
41+
// Producer returns the underlying producer. The producer can be used to understand which operations the device supports.
42+
func (d *RS485) Producer() Producer {
43+
return d.producer
44+
}
45+
46+
// Descriptor returns the device descriptor. Since this method does not have bus access the descriptor should be
47+
// prepared during initialization.
48+
func (d *RS485) Descriptor() meters.DeviceDescriptor {
5449
return meters.DeviceDescriptor{
5550
Manufacturer: d.producer.Type(),
5651
Model: d.producer.Description(),
5752
}
5853
}
5954

60-
func (d *rs485) query(client modbus.Client, op Operation) (res meters.MeasurementResult, err error) {
55+
// Probe is called by the handler after preparing the bus by setting the device id
56+
func (d *RS485) Probe(client modbus.Client) (res meters.MeasurementResult, err error) {
57+
op := d.producer.Probe()
58+
59+
res, err = d.QueryOp(client, op)
60+
if err != nil {
61+
return res, err
62+
}
63+
64+
return res, nil
65+
}
66+
67+
// QueryOp executes a single query operation on the bus
68+
func (d *RS485) QueryOp(client modbus.Client, op Operation) (res meters.MeasurementResult, err error) {
6169
var bytes []byte
6270

6371
if op.ReadLen == 0 {
@@ -90,22 +98,23 @@ func (d *rs485) query(client modbus.Client, op Operation) (res meters.Measuremen
9098
return res, nil
9199
}
92100

93-
// Probe is called by the handler after preparing the bus by setting the device id
94-
func (d *rs485) Probe(client modbus.Client) (res meters.MeasurementResult, err error) {
95-
op := d.producer.Probe()
96-
97-
res, err = d.query(client, op)
98-
if err != nil {
99-
return res, err
100-
}
101-
102-
return res, nil
103-
}
104-
105101
// Query is called by the handler after preparing the bus by setting the device id and waiting for rate limit
106-
func (d *rs485) Query(client modbus.Client) (res []meters.MeasurementResult, err error) {
102+
func (d *RS485) Query(client modbus.Client) (res []meters.MeasurementResult, err error) {
107103
res = make([]meters.MeasurementResult, 0)
108104

105+
if d.ops == nil {
106+
d.ops = make(chan Operation)
107+
108+
// ringbuffer of device operations
109+
go func(d *RS485) {
110+
for {
111+
for _, op := range d.producer.Produce() {
112+
d.ops <- op
113+
}
114+
}
115+
}(d)
116+
}
117+
109118
// Query loop will try to read all operations in a single run. It will
110119
// always start with the current inflight operation. If an error is encountered,
111120
// the partial results are returned. The loop is terminated after as many
@@ -118,7 +127,7 @@ func (d *rs485) Query(client modbus.Client) (res []meters.MeasurementResult, err
118127
d.inflight = <-d.ops
119128
}
120129

121-
m, err := d.query(client, d.inflight)
130+
m, err := d.QueryOp(client, d.inflight)
122131
if err != nil {
123132
return res, err
124133
}

meters/sunspec/sunspec.go

+53-19
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/volkszaehler/mbmd/meters"
1717
)
1818

19-
type sunSpec struct {
19+
type SunSpec struct {
2020
models []sunspec.Model
2121
descriptor meters.DeviceDescriptor
2222
}
@@ -37,15 +37,15 @@ func (e partialError) Cause() error {
3737
func (e partialError) PartiallyInitialized() {}
3838

3939
// NewDevice creates a Sunspec device
40-
func NewDevice(meterType string) meters.Device {
41-
return &sunSpec{
40+
func NewDevice(meterType string) *SunSpec {
41+
return &SunSpec{
4242
descriptor: meters.DeviceDescriptor{
4343
Manufacturer: meterType,
4444
},
4545
}
4646
}
4747

48-
func (d *sunSpec) Initialize(client modbus.Client) error {
48+
func (d *SunSpec) Initialize(client modbus.Client) error {
4949
in, err := sunspecbus.Open(client)
5050
if err != nil && in == nil {
5151
return err
@@ -79,7 +79,7 @@ func (d *sunSpec) Initialize(client modbus.Client) error {
7979
return err
8080
}
8181

82-
func (d *sunSpec) readCommonBlock(device sunspec.Device) error {
82+
func (d *SunSpec) readCommonBlock(device sunspec.Device) error {
8383
// TODO catch panic
8484
commonModel := device.MustModel(sunspec.ModelId(1))
8585
// TODO catch panic
@@ -100,7 +100,7 @@ func (d *sunSpec) readCommonBlock(device sunspec.Device) error {
100100
}
101101

102102
// collect and sort supported models except for common
103-
func (d *sunSpec) collectModels(device sunspec.Device) error {
103+
func (d *SunSpec) collectModels(device sunspec.Device) error {
104104
d.models = device.Collect(sunspec.OneOfSeveralModelIds(d.relevantModelIds()))
105105
if len(d.models) == 0 {
106106
return errors.New("sunspec: could not find supported model")
@@ -110,7 +110,7 @@ func (d *sunSpec) collectModels(device sunspec.Device) error {
110110
return nil
111111
}
112112

113-
func (d *sunSpec) relevantModelIds() []sunspec.ModelId {
113+
func (d *SunSpec) relevantModelIds() []sunspec.ModelId {
114114
modelIds := make([]sunspec.ModelId, 0, len(modelMap))
115115
for k := range modelMap {
116116
modelIds = append(modelIds, sunspec.ModelId(k))
@@ -120,7 +120,7 @@ func (d *sunSpec) relevantModelIds() []sunspec.ModelId {
120120
}
121121

122122
// remove model 101 if model 103 found
123-
func (d *sunSpec) sanitizeModels() {
123+
func (d *SunSpec) sanitizeModels() {
124124
m101 := -1
125125
for i, m := range d.models {
126126
if m.Id() == sunspec.ModelId(101) {
@@ -133,12 +133,12 @@ func (d *sunSpec) sanitizeModels() {
133133
}
134134
}
135135

136-
func (d *sunSpec) Descriptor() meters.DeviceDescriptor {
136+
func (d *SunSpec) Descriptor() meters.DeviceDescriptor {
137137
return d.descriptor
138138
}
139139

140-
func (d *sunSpec) Probe(client modbus.Client) (res meters.MeasurementResult, err error) {
141-
if d.notInitilized() {
140+
func (d *SunSpec) Probe(client modbus.Client) (res meters.MeasurementResult, err error) {
141+
if d.notInitialized() {
142142
return res, errors.New("sunspec: not initialized")
143143
}
144144

@@ -172,12 +172,11 @@ func (d *sunSpec) Probe(client modbus.Client) (res meters.MeasurementResult, err
172172
return res, fmt.Errorf("sunspec: could not find model for probe snip")
173173
}
174174

175-
func (d *sunSpec) notInitilized() bool {
175+
func (d *SunSpec) notInitialized() bool {
176176
return len(d.models) == 0
177177
}
178178

179-
func (d *sunSpec) convertPoint(b sunspec.Block, blockID int, pointID string, m meters.Measurement) (meters.MeasurementResult, error) {
180-
p := b.MustPoint(pointID)
179+
func (d *SunSpec) convertPoint(b sunspec.Block, p sunspec.Point, m meters.Measurement) (meters.MeasurementResult, error) {
181180
v := p.ScaledValue()
182181

183182
if math.IsNaN(v) {
@@ -198,24 +197,59 @@ func (d *sunSpec) convertPoint(b sunspec.Block, blockID int, pointID string, m m
198197
return mr, nil
199198
}
200199

201-
func (d *sunSpec) Query(client modbus.Client) (res []meters.MeasurementResult, err error) {
202-
if d.notInitilized() {
200+
// QueryOp executes a single query operation on the bus
201+
func (d *SunSpec) QueryOp(client modbus.Client, measurement meters.Measurement) (res meters.MeasurementResult, err error) {
202+
if d.notInitialized() {
203+
return res, errors.New("sunspec: not initialized")
204+
}
205+
206+
for _, model := range d.models {
207+
supportedID := model.Id()
208+
for modelID, blockMap := range modelMap {
209+
if modelID != supportedID {
210+
continue
211+
}
212+
213+
for blockID, pointMap := range blockMap {
214+
for pointID, m := range pointMap {
215+
if m == measurement {
216+
block := model.MustBlock(blockID)
217+
if err = block.Read(); err != nil {
218+
return
219+
}
220+
221+
point := block.MustPoint(pointID)
222+
return d.convertPoint(block, point, m)
223+
}
224+
}
225+
}
226+
}
227+
}
228+
229+
return meters.MeasurementResult{}, fmt.Errorf("sunspec: %s not found", measurement)
230+
}
231+
232+
// Query is called by the handler after preparing the bus by setting the device id and waiting for rate limit
233+
func (d *SunSpec) Query(client modbus.Client) (res []meters.MeasurementResult, err error) {
234+
if d.notInitialized() {
203235
return res, errors.New("sunspec: not initialized")
204236
}
205237

206238
for _, model := range d.models {
207239
blockID := 0
208240

209-
model.Do(func(b sunspec.Block) {
241+
model.Do(func(block sunspec.Block) {
210242
defer func() { blockID++ }()
211243

212-
if err = b.Read(); err != nil {
244+
if err = block.Read(); err != nil {
213245
return
214246
}
215247

216248
if bps, ok := modelMap[model.Id()][blockID]; ok {
217249
for pointID, m := range bps {
218-
if mr, err := d.convertPoint(b, blockID, pointID, m); err == nil {
250+
point := block.MustPoint(pointID)
251+
252+
if mr, err := d.convertPoint(block, point, m); err == nil {
219253
res = append(res, mr)
220254
}
221255
}

0 commit comments

Comments
 (0)