fixed watcher tests

This commit is contained in:
Javier Peletier
2020-12-21 11:22:31 +01:00
parent 2fbead8f56
commit 96b3f00f29
4 changed files with 78 additions and 57 deletions

4
go.mod
View File

@@ -6,9 +6,7 @@ require (
github.com/RobinUS2/golang-moving-average v1.0.0 github.com/RobinUS2/golang-moving-average v1.0.0
github.com/eclipse/paho.mqtt.golang v1.3.0 github.com/eclipse/paho.mqtt.golang v1.3.0
github.com/epiclabs-io/diff3 v0.0.0-20181217103619-05282cece609 // indirect github.com/epiclabs-io/diff3 v0.0.0-20181217103619-05282cece609 // indirect
github.com/epiclabs-io/ut v0.0.0-20190416122157-8da7fe4b4947 github.com/epiclabs-io/ut v0.0.0-20201221095005-a2a4f565f0d0
github.com/goburrow/modbus v0.1.0
github.com/goburrow/serial v0.1.0 // indirect github.com/goburrow/serial v0.1.0 // indirect
github.com/stretchr/testify v1.6.1
github.com/wz2b/modbus v0.1.1 github.com/wz2b/modbus v0.1.1
) )

14
go.sum
View File

@@ -1,24 +1,17 @@
github.com/RobinUS2/golang-moving-average v1.0.0 h1:PD7DDZNt+UFb9XlsBbTIu/DtXqqaD/MD86DYnk3mwvA= github.com/RobinUS2/golang-moving-average v1.0.0 h1:PD7DDZNt+UFb9XlsBbTIu/DtXqqaD/MD86DYnk3mwvA=
github.com/RobinUS2/golang-moving-average v1.0.0/go.mod h1:MdzhY+KoEvi+OBygTPH0OSaKrOJzvILWN2SPQzaKVsY= github.com/RobinUS2/golang-moving-average v1.0.0/go.mod h1:MdzhY+KoEvi+OBygTPH0OSaKrOJzvILWN2SPQzaKVsY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.3.0 h1:MU79lqr3FKNKbSrGN7d7bNYqh8MwWW7Zcx0iG+VIw9I= github.com/eclipse/paho.mqtt.golang v1.3.0 h1:MU79lqr3FKNKbSrGN7d7bNYqh8MwWW7Zcx0iG+VIw9I=
github.com/eclipse/paho.mqtt.golang v1.3.0/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= github.com/eclipse/paho.mqtt.golang v1.3.0/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
github.com/epiclabs-io/diff3 v0.0.0-20181217103619-05282cece609 h1:KHcpmcC/8cnCDXDm6SaCTajWF/vyUbBE1ovA27xYYEY= github.com/epiclabs-io/diff3 v0.0.0-20181217103619-05282cece609 h1:KHcpmcC/8cnCDXDm6SaCTajWF/vyUbBE1ovA27xYYEY=
github.com/epiclabs-io/diff3 v0.0.0-20181217103619-05282cece609/go.mod h1:tM499ZoH5jQRF3wlMnl59SJQwVYXIBdJRZa/K71p0IM= github.com/epiclabs-io/diff3 v0.0.0-20181217103619-05282cece609/go.mod h1:tM499ZoH5jQRF3wlMnl59SJQwVYXIBdJRZa/K71p0IM=
github.com/epiclabs-io/ut v0.0.0-20190416122157-8da7fe4b4947 h1:5jyZq+mwwE90FnIyzAorlWF0Nrg8AB48KsDxofSAyBw= github.com/epiclabs-io/ut v0.0.0-20190416122157-8da7fe4b4947 h1:5jyZq+mwwE90FnIyzAorlWF0Nrg8AB48KsDxofSAyBw=
github.com/epiclabs-io/ut v0.0.0-20190416122157-8da7fe4b4947/go.mod h1:Sm6PW7b/nLOHEn3XxuUOXFYA4xFkLUnyAWUOcTGcRZ4= github.com/epiclabs-io/ut v0.0.0-20190416122157-8da7fe4b4947/go.mod h1:Sm6PW7b/nLOHEn3XxuUOXFYA4xFkLUnyAWUOcTGcRZ4=
github.com/goburrow/modbus v0.1.0 h1:DejRZY73nEM6+bt5JSP6IsFolJ9dVcqxsYbpLbeW/ro= github.com/epiclabs-io/ut v0.0.0-20201221095005-a2a4f565f0d0 h1:mM/cqhrPWaJ3xUF4Sp4H+Zfg4kQ4/lwb1AYTg3Ig9QQ=
github.com/goburrow/modbus v0.1.0/go.mod h1:Kx552D5rLIS8E7TyUwQ/UdHEqvX5T8tyiGBTlzMcZBg= github.com/epiclabs-io/ut v0.0.0-20201221095005-a2a4f565f0d0/go.mod h1:Sm6PW7b/nLOHEn3XxuUOXFYA4xFkLUnyAWUOcTGcRZ4=
github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA= github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA= github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/wz2b/modbus v0.1.1 h1:GB9KEI8r9TDKJLFoEg59nj9FcR5Q6Glg+7EoDG8OFaE= github.com/wz2b/modbus v0.1.1 h1:GB9KEI8r9TDKJLFoEg59nj9FcR5Q6Glg+7EoDG8OFaE=
github.com/wz2b/modbus v0.1.1/go.mod h1:p74iuj8ZYGmZpurWLgFN/dw5MAOlQDTCM8veQhH+7GU= github.com/wz2b/modbus v0.1.1/go.mod h1:p74iuj8ZYGmZpurWLgFN/dw5MAOlQDTCM8veQhH+7GU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -27,6 +20,3 @@ golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,3 +1,5 @@
// Package watcher represents a cache of a range of modbus registers in a device
// Can fire events if a watched register changes
package watcher package watcher
import ( import (
@@ -7,25 +9,29 @@ import (
"sync" "sync"
) )
// Config contains the configuration parameters for a new Watcher instance
type Config struct { type Config struct {
Address uint16 Address uint16 // Start address
Quantity uint16 Quantity uint16 // Number of registers to watch
SlaveID byte SlaveID byte // SlaveID to watch
Modbus modbus.Modbus Modbus modbus.Modbus // Modbus interface
RegisterSize int RegisterSize int // size of each register
} }
// Watcher represents a cache of modbus registers in a device
type Watcher struct { type Watcher struct {
Config Config
state []byte state []byte // current view of the modbus register states
callbacks map[uint16]func(address uint16) callbacks map[uint16]func(address uint16) // set of callbacks
lock *sync.RWMutex lock *sync.RWMutex
} }
var ErrIncorrectRegisterSize = errors.New("Incorrect register size") var ErrIncorrectRegisterSize = errors.New("Incorrect register size")
var ErrAddressOutOfRange = errors.New("Register address out of range") var ErrAddressOutOfRange = errors.New("Register address out of range")
var ErrUninitialized = errors.New("State uninitialized. Call Poll() first.") var ErrUninitialized = errors.New("State uninitialized. Call Poll() first.")
var ErrCannotIncreaseRange = errors.New("Cannot increase range")
// New returns a new Watcher instance
func New(config *Config) *Watcher { func New(config *Config) *Watcher {
return &Watcher{ return &Watcher{
Config: *config, Config: *config,
@@ -34,10 +40,12 @@ func New(config *Config) *Watcher {
} }
} }
// RegisterCallback registers a new callback that will be fired when the specific register address changes values
func (w *Watcher) RegisterCallback(address uint16, callback func(address uint16)) { func (w *Watcher) RegisterCallback(address uint16, callback func(address uint16)) {
w.callbacks[address] = callback w.callbacks[address] = callback
} }
// Poll refreshes the cache by reading the watched register range from the slave device
func (w *Watcher) Poll() error { func (w *Watcher) Poll() error {
w.lock.Lock() w.lock.Lock()
newState, err := w.Modbus.ReadRegister(w.SlaveID, w.Address, w.Quantity) newState, err := w.Modbus.ReadRegister(w.SlaveID, w.Address, w.Quantity)
@@ -81,6 +89,7 @@ func (w *Watcher) Poll() error {
return nil return nil
} }
// ReadRegister reads one register from the cache
func (w *Watcher) ReadRegister(address uint16) (value []byte) { func (w *Watcher) ReadRegister(address uint16) (value []byte) {
w.lock.Lock() w.lock.Lock()
defer w.lock.Unlock() defer w.lock.Unlock()
@@ -95,6 +104,7 @@ func (w *Watcher) ReadRegister(address uint16) (value []byte) {
} }
// WriteRegister writes the value to the slave device and updates the cache if successful
func (w *Watcher) WriteRegister(address uint16, value uint16) error { func (w *Watcher) WriteRegister(address uint16, value uint16) error {
w.lock.Lock() w.lock.Lock()
results, err := w.Modbus.WriteRegister(w.SlaveID, address, value) results, err := w.Modbus.WriteRegister(w.SlaveID, address, value)
@@ -112,25 +122,26 @@ func (w *Watcher) WriteRegister(address uint16, value uint16) error {
return nil return nil
} }
// TriggerCallbacks calls all callbacks
func (w *Watcher) TriggerCallbacks() { func (w *Watcher) TriggerCallbacks() {
for address, callback := range w.callbacks { for address, callback := range w.callbacks {
callback(address) callback(address)
} }
} }
// Resize reduces the watched range
func (w *Watcher) Resize(newQuantity int) { func (w *Watcher) Resize(newQuantity int) {
w.lock.Lock() w.lock.Lock()
defer w.lock.Unlock() defer w.lock.Unlock()
if newQuantity < int(w.Quantity) { if newQuantity <= int(w.Quantity) {
w.state = w.state[:newQuantity*w.RegisterSize] w.state = w.state[:newQuantity*w.RegisterSize]
for address, _ := range w.callbacks { for address := range w.callbacks {
if address > w.Address+uint16(newQuantity)-1 { if address > w.Address+uint16(newQuantity)-1 {
delete(w.callbacks, address) delete(w.callbacks, address)
} }
} }
} else { } else {
w.state = nil panic(ErrCannotIncreaseRange)
} }
w.Quantity = uint16(newQuantity) w.Quantity = uint16(newQuantity)
} }

View File

@@ -2,84 +2,106 @@ package watcher_test
import ( import (
"errors" "errors"
"koolnova2mqtt/modbus"
"koolnova2mqtt/watcher" "koolnova2mqtt/watcher"
"testing" "testing"
"github.com/epiclabs-io/ut" "github.com/epiclabs-io/ut"
) )
var modbusError error
type BuggyModbus struct {
}
func (ms *BuggyModbus) ReadRegister(slaveID byte, address uint16, quantity uint16) (results []byte, err error) {
return []byte{1, 2}, modbusError
}
func (ms *BuggyModbus) WriteRegister(slaveID byte, address uint16, value uint16) (results []byte, err error) {
return nil, modbusError
}
func (ms *BuggyModbus) Close() error { return nil }
func TestWatcher(tx *testing.T) { func TestWatcher(tx *testing.T) {
t := ut.BeginTest(tx, false) t := ut.BeginTest(tx, false)
defer t.FinishTest() defer t.FinishTest()
var err error
var r []byte
var readRegisterError error = nil
readRegister := func(slaveID byte, address uint16, quantity uint16) (results []byte, err error) {
return r, readRegisterError
}
w := watcher.New(&watcher.Config{ w := watcher.New(&watcher.Config{
Address: 1000, Address: 1,
Quantity: 5, Quantity: 5,
RegisterSize: 2, RegisterSize: 2,
SlaveID: 1, SlaveID: 49,
Read: readRegister, Modbus: modbus.NewMock(),
}) })
var cbAddress uint16 var cbAddress uint16
var callbackCount int var callbackCount int
w.RegisterCallback(1000, func(address uint16) { w.RegisterCallback(3, func(address uint16) {
cbAddress = address cbAddress = address
callbackCount++ callbackCount++
}) })
w.RegisterCallback(1004, func(address uint16) { w.RegisterCallback(4, func(address uint16) {
callbackCount++ callbackCount++
}) })
r = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} t.MustPanicWith(watcher.ErrUninitialized, func() {
w.ReadRegister(1)
value, err := w.ReadRegister(1001) })
t.MustFailWith(err, watcher.ErrUninitialized)
t.Equals([]byte(nil), value)
err = w.Poll() err = w.Poll()
t.Ok(err) t.Ok(err)
t.Equals(2, callbackCount) t.Equals(2, callbackCount)
value, err = w.ReadRegister(1001) value := w.ReadRegister(1)
t.Ok(err) t.Ok(err)
t.Equals([]byte{3, 4}, value) t.Equals([]byte{0, 3}, value)
_, err = w.ReadRegister(200) t.MustPanicWith(watcher.ErrAddressOutOfRange, func() {
t.MustFailWith(err, watcher.ErrAddressOutOfRange) w.ReadRegister(200)
})
_, err = w.ReadRegister(5000) t.MustPanicWith(watcher.ErrAddressOutOfRange, func() {
t.MustFailWith(err, watcher.ErrAddressOutOfRange) w.ReadRegister(5000)
})
callbackCount = 0 callbackCount = 0
err = w.Poll() err = w.Poll()
t.Ok(err) t.Ok(err)
t.Equals(callbackCount, 0) t.Equals(callbackCount, 0)
r = []byte{79, 82, 3, 4, 5, 6, 7, 8, 9, 10} callbackCount = 0
err = w.WriteRegister(3, 0x1234)
t.Ok(err)
t.Equals(1, callbackCount)
t.Equals(uint16(3), cbAddress)
cbNewValue := w.ReadRegister(3)
t.Equals([]byte{0x12, 0x34}, cbNewValue)
callbackCount = 0 callbackCount = 0
err = w.Poll() err = w.Poll()
t.Ok(err) t.Ok(err)
t.Equals(0, callbackCount)
t.Equals(1, callbackCount) w.TriggerCallbacks()
t.Equals(uint16(1000), cbAddress) t.Equals(2, callbackCount)
cbNewValue, err := w.ReadRegister(cbAddress) w = watcher.New(&watcher.Config{
t.Ok(err) Address: 1,
t.Equals([]byte{79, 82}, cbNewValue) Quantity: 5,
RegisterSize: 2,
SlaveID: 1,
Modbus: &BuggyModbus{},
})
r = []byte{1, 2}
err = w.Poll() err = w.Poll()
t.MustFailWith(err, watcher.ErrIncorrectRegisterSize) t.MustFailWith(err, watcher.ErrIncorrectRegisterSize)
readRegisterError = errors.New("error") modbusError = errors.New("error")
err = w.Poll() err = w.Poll()
t.MustFail(err, "expected Poll() to fail if readRegister returns error") t.MustFail(err, "expected Poll() to fail if readRegister returns error")