mirror of
https://github.com/jpeletier/koolnova2mqtt.git
synced 2026-07-27 20:06:59 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type ReadRegister func(slaveID byte, address uint16, quantity uint16) (results []byte, err error)
|
||||
|
||||
type Config struct {
|
||||
Address uint16
|
||||
Quantity uint16
|
||||
SlaveID byte
|
||||
Read ReadRegister
|
||||
RegisterSize int
|
||||
}
|
||||
|
||||
type watcher struct {
|
||||
Config
|
||||
state []byte
|
||||
callbacks map[uint16]func(address uint16)
|
||||
}
|
||||
|
||||
var ErrIncorrectRegisterSize = errors.New("Incorrect register size")
|
||||
var ErrAddressOutOfRange = errors.New("Register address out of range")
|
||||
var ErrUninitialized = errors.New("State uninitialized. Call Poll() first.")
|
||||
|
||||
func New(config *Config) *watcher {
|
||||
return &watcher{
|
||||
Config: *config,
|
||||
callbacks: make(map[uint16]func(address uint16)),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) RegisterCallback(address uint16, callback func(address uint16)) {
|
||||
w.callbacks[address] = callback
|
||||
}
|
||||
|
||||
func (w *watcher) Poll() error {
|
||||
newState, err := w.Read(w.SlaveID, w.Address, w.Quantity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(newState) != int(w.Quantity)*w.RegisterSize {
|
||||
return ErrIncorrectRegisterSize
|
||||
}
|
||||
|
||||
oldState := w.state
|
||||
w.state = newState
|
||||
|
||||
first := len(oldState) != len(newState)
|
||||
address := w.Address
|
||||
for n := 0; n < len(newState); n += w.RegisterSize {
|
||||
callback := w.callbacks[address]
|
||||
if callback == nil {
|
||||
address++
|
||||
continue
|
||||
}
|
||||
var oldValue []byte
|
||||
newValue := newState[n : n+w.RegisterSize]
|
||||
if first {
|
||||
oldValue = nil
|
||||
} else {
|
||||
oldValue = oldState[n : n+w.RegisterSize]
|
||||
}
|
||||
if bytes.Compare(oldValue, newValue) != 0 {
|
||||
callback(address)
|
||||
}
|
||||
address++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *watcher) ReadRegister(address uint16) (value []byte, err error) {
|
||||
if address < w.Address || address > w.Address+uint16(w.Quantity) {
|
||||
return nil, ErrAddressOutOfRange
|
||||
}
|
||||
if w.state == nil {
|
||||
return nil, ErrUninitialized
|
||||
}
|
||||
registerOffset := int(address-w.Address) * w.RegisterSize
|
||||
return w.state[registerOffset : registerOffset+w.RegisterSize], nil
|
||||
|
||||
}
|
||||
|
||||
func (w *watcher) TriggerCallbacks() {
|
||||
for address, callback := range w.callbacks {
|
||||
callback(address)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package watcher_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"koolnova2mqtt/watcher"
|
||||
"testing"
|
||||
|
||||
"github.com/epiclabs-io/ut"
|
||||
)
|
||||
|
||||
func TestWatcher(tx *testing.T) {
|
||||
t := ut.BeginTest(tx, false)
|
||||
defer t.FinishTest()
|
||||
|
||||
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{
|
||||
Address: 1000,
|
||||
Quantity: 5,
|
||||
RegisterSize: 2,
|
||||
SlaveID: 1,
|
||||
Read: readRegister,
|
||||
})
|
||||
|
||||
var cbAddress uint16
|
||||
var callbackCount int
|
||||
w.RegisterCallback(1000, func(address uint16) {
|
||||
cbAddress = address
|
||||
callbackCount++
|
||||
})
|
||||
|
||||
w.RegisterCallback(1004, func(address uint16) {
|
||||
callbackCount++
|
||||
})
|
||||
|
||||
r = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
|
||||
value, err := w.ReadRegister(1001)
|
||||
t.MustFailWith(err, watcher.ErrUninitialized)
|
||||
t.Equals([]byte(nil), value)
|
||||
|
||||
err = w.Poll()
|
||||
t.Ok(err)
|
||||
t.Equals(2, callbackCount)
|
||||
|
||||
value, err = w.ReadRegister(1001)
|
||||
t.Ok(err)
|
||||
t.Equals([]byte{3, 4}, value)
|
||||
|
||||
_, err = w.ReadRegister(200)
|
||||
t.MustFailWith(err, watcher.ErrAddressOutOfRange)
|
||||
|
||||
_, err = w.ReadRegister(5000)
|
||||
t.MustFailWith(err, watcher.ErrAddressOutOfRange)
|
||||
|
||||
callbackCount = 0
|
||||
err = w.Poll()
|
||||
t.Ok(err)
|
||||
|
||||
t.Equals(callbackCount, 0)
|
||||
|
||||
r = []byte{79, 82, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
callbackCount = 0
|
||||
err = w.Poll()
|
||||
t.Ok(err)
|
||||
|
||||
t.Equals(1, callbackCount)
|
||||
t.Equals(uint16(1000), cbAddress)
|
||||
|
||||
cbNewValue, err := w.ReadRegister(cbAddress)
|
||||
t.Ok(err)
|
||||
t.Equals([]byte{79, 82}, cbNewValue)
|
||||
|
||||
r = []byte{1, 2}
|
||||
err = w.Poll()
|
||||
t.MustFailWith(err, watcher.ErrIncorrectRegisterSize)
|
||||
|
||||
readRegisterError = errors.New("error")
|
||||
|
||||
err = w.Poll()
|
||||
t.MustFail(err, "expected Poll() to fail if readRegister returns error")
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user