Files
koolnova2mqtt/watcher/watcher.go
T

137 lines
3.0 KiB
Go

package watcher
import (
"bytes"
"errors"
"koolnova2mqtt/modbus"
"sync"
)
type Config struct {
Address uint16
Quantity uint16
SlaveID byte
Modbus modbus.Modbus
RegisterSize int
}
type Watcher struct {
Config
state []byte
callbacks map[uint16]func(address uint16)
lock *sync.RWMutex
}
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)),
lock: &sync.RWMutex{},
}
}
func (w *Watcher) RegisterCallback(address uint16, callback func(address uint16)) {
w.callbacks[address] = callback
}
func (w *Watcher) Poll() error {
w.lock.Lock()
newState, err := w.Modbus.ReadRegister(w.SlaveID, w.Address, w.Quantity)
if err != nil {
w.lock.Unlock()
return err
}
if len(newState) != int(w.Quantity)*w.RegisterSize {
w.lock.Unlock()
return ErrIncorrectRegisterSize
}
oldState := w.state
w.state = newState
var callbackAddresses []uint16
first := len(oldState) != len(newState)
for n := 0; n < len(newState); n += w.RegisterSize {
address := uint16(n/w.RegisterSize) + w.Address
callback := w.callbacks[address]
if callback == nil {
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 {
callbackAddresses = append(callbackAddresses, address)
}
}
w.lock.Unlock()
for _, address := range callbackAddresses {
callback := w.callbacks[address]
callback(address)
}
return nil
}
func (w *Watcher) ReadRegister(address uint16) (value []byte) {
w.lock.Lock()
defer w.lock.Unlock()
if address < w.Address || address > w.Address+uint16(w.Quantity) {
panic(ErrAddressOutOfRange)
}
if w.state == nil {
panic(ErrUninitialized)
}
registerOffset := int(address-w.Address) * w.RegisterSize
return w.state[registerOffset : registerOffset+w.RegisterSize]
}
func (w *Watcher) WriteRegister(address uint16, value uint16) error {
w.lock.Lock()
results, err := w.Modbus.WriteRegister(w.SlaveID, address, value)
if err != nil {
w.lock.Unlock()
return err
}
registerOffset := int(address-w.Address) * w.RegisterSize
copy(w.state[registerOffset:registerOffset+w.RegisterSize], results)
callback := w.callbacks[address]
w.lock.Unlock()
if callback != nil {
callback(address)
}
return nil
}
func (w *Watcher) TriggerCallbacks() {
for address, callback := range w.callbacks {
callback(address)
}
}
func (w *Watcher) Resize(newQuantity int) {
w.lock.Lock()
defer w.lock.Unlock()
if newQuantity < int(w.Quantity) {
w.state = w.state[:newQuantity*w.RegisterSize]
for address, _ := range w.callbacks {
if address > w.Address+uint16(newQuantity)-1 {
delete(w.callbacks, address)
}
}
} else {
w.state = nil
}
w.Quantity = uint16(newQuantity)
}