mirror of
https://github.com/jpeletier/koolnova2mqtt.git
synced 2026-01-11 15:11:43 +00:00
cleanup, comments, tests
This commit is contained in:
151
bimap/bimap.go
151
bimap/bimap.go
@@ -1,151 +0,0 @@
|
|||||||
package bimap
|
|
||||||
|
|
||||||
// Package bimap provides a threadsafe bidirectional map
|
|
||||||
// original implementation by @vishalkuo: https://github.com/vishalkuo/bimap/
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
// BiMap is a bi-directional hashmap that is thread safe and supports immutability
|
|
||||||
type BiMap struct {
|
|
||||||
s sync.RWMutex
|
|
||||||
immutable bool
|
|
||||||
forward map[interface{}]interface{}
|
|
||||||
inverse map[interface{}]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a an empty, mutable, biMap
|
|
||||||
func New(content map[interface{}]interface{}) *BiMap {
|
|
||||||
b := &BiMap{forward: make(map[interface{}]interface{}), inverse: make(map[interface{}]interface{}), immutable: false}
|
|
||||||
if content != nil {
|
|
||||||
for k, v := range content {
|
|
||||||
b.Insert(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert puts a key and value into the BiMap, provided its mutable. Also creates the reverse mapping from value to key.
|
|
||||||
func (b *BiMap) Insert(k interface{}, v interface{}) {
|
|
||||||
b.s.RLock()
|
|
||||||
if b.immutable {
|
|
||||||
panic("Cannot modify immutable map")
|
|
||||||
}
|
|
||||||
b.s.RUnlock()
|
|
||||||
|
|
||||||
b.s.Lock()
|
|
||||||
defer b.s.Unlock()
|
|
||||||
b.forward[k] = v
|
|
||||||
b.inverse[v] = k
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists checks whether or not a key exists in the BiMap
|
|
||||||
func (b *BiMap) Exists(k interface{}) bool {
|
|
||||||
b.s.RLock()
|
|
||||||
defer b.s.RUnlock()
|
|
||||||
_, ok := b.forward[k]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExistsInverse checks whether or not a value exists in the BiMap
|
|
||||||
func (b *BiMap) ExistsInverse(k interface{}) bool {
|
|
||||||
b.s.RLock()
|
|
||||||
defer b.s.RUnlock()
|
|
||||||
|
|
||||||
_, ok := b.inverse[k]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the value for a given key in the BiMap and whether or not the element was present.
|
|
||||||
func (b *BiMap) Get(k interface{}) (interface{}, bool) {
|
|
||||||
if !b.Exists(k) {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
b.s.RLock()
|
|
||||||
defer b.s.RUnlock()
|
|
||||||
return b.forward[k], true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInverse returns the key for a given value in the BiMap and whether or not the element was present.
|
|
||||||
func (b *BiMap) GetInverse(v interface{}) (interface{}, bool) {
|
|
||||||
if !b.ExistsInverse(v) {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
b.s.RLock()
|
|
||||||
defer b.s.RUnlock()
|
|
||||||
return b.inverse[v], true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes a key-value pair from the BiMap for a given key. Returns if the key doesn't exist
|
|
||||||
func (b *BiMap) Delete(k interface{}) {
|
|
||||||
b.s.RLock()
|
|
||||||
if b.immutable {
|
|
||||||
panic("Cannot modify immutable map")
|
|
||||||
}
|
|
||||||
b.s.RUnlock()
|
|
||||||
|
|
||||||
if !b.Exists(k) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val, _ := b.Get(k)
|
|
||||||
b.s.Lock()
|
|
||||||
defer b.s.Unlock()
|
|
||||||
delete(b.forward, k)
|
|
||||||
delete(b.inverse, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteInverse emoves a key-value pair from the BiMap for a given value. Returns if the value doesn't exist
|
|
||||||
func (b *BiMap) DeleteInverse(v interface{}) {
|
|
||||||
b.s.RLock()
|
|
||||||
if b.immutable {
|
|
||||||
panic("Cannot modify immutable map")
|
|
||||||
}
|
|
||||||
b.s.RUnlock()
|
|
||||||
|
|
||||||
if !b.ExistsInverse(v) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key, _ := b.GetInverse(v)
|
|
||||||
b.s.Lock()
|
|
||||||
defer b.s.Unlock()
|
|
||||||
delete(b.inverse, v)
|
|
||||||
delete(b.forward, key)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the number of elements in the bimap
|
|
||||||
func (b *BiMap) Size() int {
|
|
||||||
b.s.RLock()
|
|
||||||
defer b.s.RUnlock()
|
|
||||||
return len(b.forward)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeImmutable freezes the BiMap preventing any further write actions from taking place
|
|
||||||
func (b *BiMap) MakeImmutable() {
|
|
||||||
b.s.Lock()
|
|
||||||
defer b.s.Unlock()
|
|
||||||
b.immutable = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInverseMap returns a regular go map mapping from the BiMap's values to its keys
|
|
||||||
func (b *BiMap) GetInverseMap() map[interface{}]interface{} {
|
|
||||||
return b.inverse
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetForwardMap returns a regular go map mapping from the BiMap's keys to its values
|
|
||||||
func (b *BiMap) GetForwardMap() map[interface{}]interface{} {
|
|
||||||
return b.forward
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock manually locks the BiMap's mutex
|
|
||||||
func (b *BiMap) Lock() {
|
|
||||||
b.s.Lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlock manually unlocks the BiMap's mutex
|
|
||||||
func (b *BiMap) Unlock() {
|
|
||||||
b.s.Unlock()
|
|
||||||
}
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
package bimap
|
|
||||||
|
|
||||||
// original implementation by @vishalkuo: https://github.com/vishalkuo/bimap/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"runtime/debug"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/epiclabs-io/ut"
|
|
||||||
)
|
|
||||||
|
|
||||||
const key = "key"
|
|
||||||
const value = "value"
|
|
||||||
|
|
||||||
// isEmpty gets whether the specified object is considered empty or not.
|
|
||||||
func isEmpty(object interface{}) bool {
|
|
||||||
|
|
||||||
// get nil case out of the way
|
|
||||||
if object == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
objValue := reflect.ValueOf(object)
|
|
||||||
|
|
||||||
switch objValue.Kind() {
|
|
||||||
// collection types are empty when they have no element
|
|
||||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
|
||||||
return objValue.Len() == 0
|
|
||||||
// pointers are empty if nil or if the value they point to is empty
|
|
||||||
case reflect.Ptr:
|
|
||||||
if objValue.IsNil() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
deref := objValue.Elem().Interface()
|
|
||||||
return isEmpty(deref)
|
|
||||||
// for all other types, compare against the zero value
|
|
||||||
default:
|
|
||||||
zero := reflect.Zero(objValue.Type())
|
|
||||||
return reflect.DeepEqual(object, zero.Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// didPanic returns true if the function passed to it panics. Otherwise, it returns false.
|
|
||||||
func didPanic(f func()) (bool, interface{}, string) {
|
|
||||||
|
|
||||||
didPanic := false
|
|
||||||
var message interface{}
|
|
||||||
var stack string
|
|
||||||
func() {
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if message = recover(); message != nil {
|
|
||||||
didPanic = true
|
|
||||||
stack = string(debug.Stack())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// call the target function
|
|
||||||
f()
|
|
||||||
|
|
||||||
}()
|
|
||||||
|
|
||||||
return didPanic, message, stack
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNew(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
|
|
||||||
actual := New(nil)
|
|
||||||
expected := &BiMap{forward: make(map[interface{}]interface{}), inverse: make(map[interface{}]interface{})}
|
|
||||||
t.Equals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_Insert(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
actual.Insert(key, value)
|
|
||||||
|
|
||||||
fwdExpected := make(map[interface{}]interface{})
|
|
||||||
invExpected := make(map[interface{}]interface{})
|
|
||||||
fwdExpected[key] = value
|
|
||||||
invExpected[value] = key
|
|
||||||
expected := &BiMap{forward: fwdExpected, inverse: invExpected}
|
|
||||||
|
|
||||||
t.Equals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_Exists(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
|
|
||||||
actual.Insert(key, value)
|
|
||||||
t.Assert(!actual.Exists("ARBITARY_KEY"), "Key should not exist")
|
|
||||||
t.Assert(actual.Exists(key), "Inserted key should exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_InverseExists(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
|
|
||||||
actual.Insert(key, value)
|
|
||||||
t.Assert(!actual.ExistsInverse("ARBITARY_VALUE"), "Value should not exist")
|
|
||||||
t.Assert(actual.ExistsInverse(value), "Inserted value should exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_Get(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
|
|
||||||
actual.Insert(key, value)
|
|
||||||
|
|
||||||
actualVal, ok := actual.Get(key)
|
|
||||||
|
|
||||||
t.Assert(ok, "It should return true")
|
|
||||||
t.Equals(value, actualVal)
|
|
||||||
|
|
||||||
actualVal, ok = actual.Get(value)
|
|
||||||
|
|
||||||
t.Assert(!ok, "It should return false")
|
|
||||||
t.Assert(isEmpty(actualVal), "Actual val should be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_GetInverse(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
|
|
||||||
actual.Insert(key, value)
|
|
||||||
|
|
||||||
actualKey, ok := actual.GetInverse(value)
|
|
||||||
|
|
||||||
t.Assert(ok, "It should return true")
|
|
||||||
t.Equals(key, actualKey)
|
|
||||||
|
|
||||||
actualKey, ok = actual.Get(value)
|
|
||||||
|
|
||||||
t.Assert(!ok, "It should return false")
|
|
||||||
t.Assert(isEmpty(actualKey), "Actual key should be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_Size(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
|
|
||||||
t.Equals(0, actual.Size())
|
|
||||||
|
|
||||||
actual.Insert(key, value)
|
|
||||||
|
|
||||||
t.Equals(1, actual.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_Delete(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
dummyKey := "DummyKey"
|
|
||||||
dummyVal := "DummyVal"
|
|
||||||
actual.Insert(key, value)
|
|
||||||
actual.Insert(dummyKey, dummyVal)
|
|
||||||
|
|
||||||
t.Equals(2, actual.Size())
|
|
||||||
|
|
||||||
actual.Delete(dummyKey)
|
|
||||||
|
|
||||||
fwdExpected := make(map[interface{}]interface{})
|
|
||||||
invExpected := make(map[interface{}]interface{})
|
|
||||||
fwdExpected[key] = value
|
|
||||||
invExpected[value] = key
|
|
||||||
|
|
||||||
expected := &BiMap{forward: fwdExpected, inverse: invExpected}
|
|
||||||
|
|
||||||
t.Equals(1, actual.Size())
|
|
||||||
t.Equals(expected, actual)
|
|
||||||
|
|
||||||
actual.Delete(dummyKey)
|
|
||||||
|
|
||||||
t.Equals(1, actual.Size())
|
|
||||||
t.Equals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_InverseDelete(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
dummyKey := "DummyKey"
|
|
||||||
dummyVal := "DummyVal"
|
|
||||||
actual.Insert(key, value)
|
|
||||||
actual.Insert(dummyKey, dummyVal)
|
|
||||||
|
|
||||||
t.Equals(2, actual.Size())
|
|
||||||
|
|
||||||
actual.DeleteInverse(dummyVal)
|
|
||||||
|
|
||||||
fwdExpected := make(map[interface{}]interface{})
|
|
||||||
invExpected := make(map[interface{}]interface{})
|
|
||||||
fwdExpected[key] = value
|
|
||||||
invExpected[value] = key
|
|
||||||
|
|
||||||
expected := &BiMap{forward: fwdExpected, inverse: invExpected}
|
|
||||||
|
|
||||||
t.Equals(1, actual.Size())
|
|
||||||
t.Equals(expected, actual)
|
|
||||||
|
|
||||||
actual.DeleteInverse(dummyVal)
|
|
||||||
|
|
||||||
t.Equals(1, actual.Size())
|
|
||||||
t.Equals(expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_WithVaryingType(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
dummyKey := "Dummy key"
|
|
||||||
dummyVal := 3
|
|
||||||
|
|
||||||
actual.Insert(dummyKey, dummyVal)
|
|
||||||
|
|
||||||
res, _ := actual.Get(dummyKey)
|
|
||||||
resVal, _ := actual.GetInverse(dummyVal)
|
|
||||||
t.Equals(dummyVal, res)
|
|
||||||
t.Equals(dummyKey, resVal)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_MakeImmutable(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
dummyKey := "Dummy key"
|
|
||||||
dummyVal := 3
|
|
||||||
|
|
||||||
actual.Insert(dummyKey, dummyVal)
|
|
||||||
|
|
||||||
actual.MakeImmutable()
|
|
||||||
|
|
||||||
panicked, _, _ := didPanic(func() {
|
|
||||||
actual.Delete(dummyKey)
|
|
||||||
})
|
|
||||||
t.Assert(panicked, "It should panic on a mutation operation")
|
|
||||||
|
|
||||||
val, _ := actual.Get(dummyKey)
|
|
||||||
|
|
||||||
t.Equals(dummyVal, val)
|
|
||||||
|
|
||||||
panicked, _, _ = didPanic(func() {
|
|
||||||
actual.DeleteInverse(dummyVal)
|
|
||||||
})
|
|
||||||
t.Assert(panicked, "It should panic on a mutation operation")
|
|
||||||
|
|
||||||
key, _ := actual.GetInverse(dummyVal)
|
|
||||||
|
|
||||||
t.Equals(dummyKey, key)
|
|
||||||
|
|
||||||
size := actual.Size()
|
|
||||||
|
|
||||||
t.Equals(1, size)
|
|
||||||
|
|
||||||
panicked, _, _ = didPanic(func() {
|
|
||||||
actual.Insert("New", 1)
|
|
||||||
})
|
|
||||||
t.Assert(panicked, "It should panic on a mutation operation")
|
|
||||||
|
|
||||||
size = actual.Size()
|
|
||||||
|
|
||||||
t.Equals(1, size)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_GetForwardMap(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
dummyKey := "Dummy key"
|
|
||||||
dummyVal := 42
|
|
||||||
|
|
||||||
forwardMap := make(map[interface{}]interface{})
|
|
||||||
forwardMap[dummyKey] = dummyVal
|
|
||||||
|
|
||||||
actual.Insert(dummyKey, dummyVal)
|
|
||||||
|
|
||||||
actualForwardMap := actual.GetForwardMap()
|
|
||||||
eq := reflect.DeepEqual(actualForwardMap, forwardMap)
|
|
||||||
t.Assert(eq, "Forward maps should be equal")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_GetInverseMap(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
actual := New(nil)
|
|
||||||
dummyKey := "Dummy key"
|
|
||||||
dummyVal := 42
|
|
||||||
|
|
||||||
inverseMap := make(map[interface{}]interface{})
|
|
||||||
inverseMap[dummyVal] = dummyKey
|
|
||||||
|
|
||||||
actual.Insert(dummyKey, dummyVal)
|
|
||||||
|
|
||||||
actualInverseMap := actual.GetInverseMap()
|
|
||||||
eq := reflect.DeepEqual(actualInverseMap, inverseMap)
|
|
||||||
t.Assert(eq, "Inverse maps should be equal")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBiMap_Initialize(tx *testing.T) {
|
|
||||||
t := ut.BeginTest(tx, false)
|
|
||||||
defer t.FinishTest()
|
|
||||||
|
|
||||||
content := map[interface{}]interface{}{
|
|
||||||
"a": 1,
|
|
||||||
"b": 2,
|
|
||||||
"c": 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
m := New(content)
|
|
||||||
|
|
||||||
v, present := m.Get("a")
|
|
||||||
t.Equals(1, v)
|
|
||||||
t.Equals(true, present)
|
|
||||||
|
|
||||||
k, present := m.GetInverse(3)
|
|
||||||
t.Equals("c", k)
|
|
||||||
t.Equals(true, present)
|
|
||||||
|
|
||||||
}
|
|
||||||
175
kn/bridge.go
175
kn/bridge.go
@@ -3,43 +3,46 @@ package kn
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"koolnova2mqtt/modbus"
|
|
||||||
"koolnova2mqtt/watcher"
|
"koolnova2mqtt/watcher"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MqttClient defines the expected MQTT client pub sub interface
|
||||||
type MqttClient interface {
|
type MqttClient interface {
|
||||||
Publish(topic string, qos byte, retained bool, payload string) error
|
Publish(topic string, qos byte, retained bool, payload string) error
|
||||||
Subscribe(topic string, callback func(message string)) error
|
Subscribe(topic string, callback func(message string)) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config defines de Modbus<>MQTT bridge configuration
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ModuleName string
|
ModuleName string // name of the module the modbus interface is connected to
|
||||||
SlaveID byte
|
SlaveID byte // SlaveID of the module in the bus
|
||||||
Mqtt MqttClient
|
TopicPrefix string // MQTT topic prefix to publish information
|
||||||
TopicPrefix string
|
HassPrefix string // Home Assistant sensor discovery prefix
|
||||||
HassPrefix string
|
Mqtt MqttClient // MQTT client
|
||||||
Modbus modbus.Modbus
|
Modbus watcher.Modbus // Modbus client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bridge bridges Modbus and MQTT protocols
|
||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
Config
|
Config // embedded configuration
|
||||||
zw *watcher.Watcher
|
zw *watcher.Watcher // watcher to detect register changes in zones
|
||||||
sysw *watcher.Watcher
|
sysw *watcher.Watcher // watcher to detect register changes in system registers
|
||||||
refresh func()
|
zones []*Zone // List of present zones in this module
|
||||||
zones []*Zone
|
sys *SysDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getActiveZones returns the list of active zones in this module
|
||||||
func getActiveZones(w Watcher) ([]*Zone, error) {
|
func getActiveZones(w Watcher) ([]*Zone, error) {
|
||||||
var zones []*Zone
|
var zones []*Zone
|
||||||
|
|
||||||
for n := 0; n < NUM_ZONES; n++ {
|
for n := 0; n < NUM_ZONES; n++ {
|
||||||
zone := NewZone(&ZoneConfig{
|
zone := newZone(&ZoneConfig{
|
||||||
ZoneNumber: n + 1,
|
ZoneNumber: n + 1,
|
||||||
Watcher: w,
|
Watcher: w,
|
||||||
})
|
})
|
||||||
isPresent := zone.IsPresent()
|
isPresent := zone.isPresent()
|
||||||
if isPresent {
|
if isPresent {
|
||||||
zones = append(zones, zone)
|
zones = append(zones, zone)
|
||||||
}
|
}
|
||||||
@@ -47,6 +50,7 @@ func getActiveZones(w Watcher) ([]*Zone, error) {
|
|||||||
return zones, nil
|
return zones, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewBridge returns a new Modbus<>MQTT bridge
|
||||||
func NewBridge(config *Config) *Bridge {
|
func NewBridge(config *Config) *Bridge {
|
||||||
b := &Bridge{
|
b := &Bridge{
|
||||||
Config: *config,
|
Config: *config,
|
||||||
@@ -54,8 +58,11 @@ func NewBridge(config *Config) *Bridge {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start starts the bridge, publishes the configuration to Home Assistant topics and publishes
|
||||||
|
// the current state. Call Start() every time MQTT gets disconnected.
|
||||||
func (b *Bridge) Start() error {
|
func (b *Bridge) Start() error {
|
||||||
|
|
||||||
|
// Define a watcher to watch the zone registers
|
||||||
zw := watcher.New(&watcher.Config{
|
zw := watcher.New(&watcher.Config{
|
||||||
Address: FIRST_ZONE_REGISTER,
|
Address: FIRST_ZONE_REGISTER,
|
||||||
Quantity: TOTAL_ZONE_REGISTERS,
|
Quantity: TOTAL_ZONE_REGISTERS,
|
||||||
@@ -63,17 +70,20 @@ func (b *Bridge) Start() error {
|
|||||||
Modbus: b.Modbus,
|
Modbus: b.Modbus,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Define a watcher to watch the system registers
|
||||||
sysw := watcher.New(&watcher.Config{
|
sysw := watcher.New(&watcher.Config{
|
||||||
Address: FIRST_SYS_REGISTER,
|
Address: FIRST_SYS_REGISTER,
|
||||||
Quantity: TOTAL_SYS_REGISTERS,
|
Quantity: TOTAL_SYS_REGISTERS,
|
||||||
SlaveID: b.SlaveID,
|
SlaveID: b.SlaveID,
|
||||||
Modbus: b.Modbus,
|
Modbus: b.Modbus,
|
||||||
})
|
})
|
||||||
|
|
||||||
b.zw = zw
|
b.zw = zw
|
||||||
b.sysw = sysw
|
b.sysw = sysw
|
||||||
sys := NewSys(&SysConfig{
|
sys := NewSys(&SysConfig{
|
||||||
Watcher: b.sysw,
|
Watcher: b.sysw,
|
||||||
})
|
})
|
||||||
|
b.sys = sys
|
||||||
|
|
||||||
log.Printf("Starting bridge for %s\n", b.ModuleName)
|
log.Printf("Starting bridge for %s\n", b.ModuleName)
|
||||||
err := b.poll()
|
err := b.poll()
|
||||||
@@ -81,49 +91,18 @@ func (b *Bridge) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get Active zones
|
||||||
zones, err := getActiveZones(b.zw)
|
zones, err := getActiveZones(b.zw)
|
||||||
b.zones = zones
|
b.zones = zones
|
||||||
log.Printf("%d zones are present in %s\n", len(zones), b.ModuleName)
|
log.Printf("%d zones are present in %s\n", len(zones), b.ModuleName)
|
||||||
|
|
||||||
|
// Downsize watched range to the required number of registers
|
||||||
b.zw.Resize(len(zones) * REG_PER_ZONE)
|
b.zw.Resize(len(zones) * REG_PER_ZONE)
|
||||||
|
|
||||||
getHVACMode := func() string {
|
|
||||||
if !sys.GetSystemEnabled() {
|
|
||||||
return HVAC_MODE_OFF
|
|
||||||
}
|
|
||||||
switch sys.GetSystemKNMode() {
|
|
||||||
case MODE_AIR_COOLING, MODE_UNDERFLOOR_AIR_COOLING:
|
|
||||||
return HVAC_MODE_COOL
|
|
||||||
case MODE_AIR_HEATING, MODE_UNDERFLOOR_HEATING, MODE_UNDERFLOOR_AIR_HEATING:
|
|
||||||
return HVAC_MODE_HEAT
|
|
||||||
}
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
getHoldMode := func() string {
|
|
||||||
switch sys.GetSystemKNMode() {
|
|
||||||
case MODE_AIR_COOLING, MODE_AIR_HEATING:
|
|
||||||
return HOLD_MODE_FAN_ONLY
|
|
||||||
case MODE_UNDERFLOOR_HEATING:
|
|
||||||
return HOLD_MODE_UNDERFLOOR_ONLY
|
|
||||||
case MODE_UNDERFLOOR_AIR_COOLING, MODE_UNDERFLOOR_AIR_HEATING:
|
|
||||||
return HOLD_MODE_UNDERFLOOR_AND_FAN
|
|
||||||
}
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
publishHvacMode := func() {
|
|
||||||
for _, zone := range zones {
|
|
||||||
if zone.IsOn() {
|
|
||||||
hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode")
|
|
||||||
mode := getHVACMode()
|
|
||||||
b.Mqtt.Publish(hvacModeTopic, 0, true, mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
holdModeTopic := b.getSysTopic("holdMode")
|
holdModeTopic := b.getSysTopic("holdMode")
|
||||||
holdModeSetTopic := holdModeTopic + "/set"
|
holdModeSetTopic := holdModeTopic + "/set"
|
||||||
|
|
||||||
|
// configure publishing when modbus registers change
|
||||||
for _, zone := range zones {
|
for _, zone := range zones {
|
||||||
zone := zone
|
zone := zone
|
||||||
currentTempTopic := b.getZoneTopic(zone.ZoneNumber, "currentTemp")
|
currentTempTopic := b.getZoneTopic(zone.ZoneNumber, "currentTemp")
|
||||||
@@ -134,36 +113,45 @@ func (b *Bridge) Start() error {
|
|||||||
hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode")
|
hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode")
|
||||||
hvacModeSetTopic := hvacModeTopic + "/set"
|
hvacModeSetTopic := hvacModeTopic + "/set"
|
||||||
|
|
||||||
|
// In HA there is three HVAC modes: "cool", "heat" and "off". Therefore,
|
||||||
|
// publish "OFF" if we detect the REG_ENABLED change is off
|
||||||
|
// Otherwise, publish "heat" or "cool" depending on REG_MODE
|
||||||
zone.OnEnabledChange = func() {
|
zone.OnEnabledChange = func() {
|
||||||
hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode")
|
hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode")
|
||||||
var mode string
|
var mode string
|
||||||
if zone.IsOn() {
|
if zone.isOn() {
|
||||||
mode = getHVACMode()
|
mode = sys.HVACMode()
|
||||||
} else {
|
} else {
|
||||||
mode = HVAC_MODE_OFF
|
mode = HVAC_MODE_OFF
|
||||||
}
|
}
|
||||||
b.Mqtt.Publish(hvacModeTopic, 0, true, mode)
|
b.Mqtt.Publish(hvacModeTopic, 0, true, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the current temperature changes, forward value to the
|
||||||
|
// correspondig MQTT topic
|
||||||
zone.OnCurrentTempChange = func(currentTemp float32) {
|
zone.OnCurrentTempChange = func(currentTemp float32) {
|
||||||
b.Mqtt.Publish(currentTempTopic, 0, true, fmt.Sprintf("%g", currentTemp))
|
b.Mqtt.Publish(currentTempTopic, 0, true, fmt.Sprintf("%g", currentTemp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the target temperature changes, publish it to MQTT
|
||||||
|
// this is fired when the target is set over MQTT or via a thermostat
|
||||||
zone.OnTargetTempChange = func(targetTemp float32) {
|
zone.OnTargetTempChange = func(targetTemp float32) {
|
||||||
b.Mqtt.Publish(targetTempTopic, 0, true, fmt.Sprintf("%g", targetTemp))
|
b.Mqtt.Publish(targetTempTopic, 0, true, fmt.Sprintf("%g", targetTemp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Publish changes to the fan mode
|
||||||
zone.OnFanModeChange = func(fanMode FanMode) {
|
zone.OnFanModeChange = func(fanMode FanMode) {
|
||||||
b.Mqtt.Publish(fanModeTopic, 0, true, FanMode2Str(fanMode))
|
b.Mqtt.Publish(fanModeTopic, 0, true, FanMode2Str(fanMode))
|
||||||
}
|
}
|
||||||
zone.OnKnModeChange = func(knMode KnMode) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Subscribe to target temperature set topic in MQTT
|
||||||
err = b.Mqtt.Subscribe(targetTempSetTopic, func(message string) {
|
err = b.Mqtt.Subscribe(targetTempSetTopic, func(message string) {
|
||||||
targetTemp, err := strconv.ParseFloat(message, 32)
|
targetTemp, err := strconv.ParseFloat(message, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing targetTemperature in topic %s: %s", targetTempSetTopic, err)
|
log.Printf("Error parsing targetTemperature in topic %s: %s", targetTempSetTopic, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = zone.SetTargetTemperature(float32(targetTemp))
|
err = zone.setTargetTemperature(float32(targetTemp))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot set target temperature to %g in zone %d", targetTemp, zone.ZoneNumber)
|
log.Printf("Cannot set target temperature to %g in zone %d", targetTemp, zone.ZoneNumber)
|
||||||
}
|
}
|
||||||
@@ -172,12 +160,13 @@ func (b *Bridge) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subscribe to fan mode set topic in MQTT
|
||||||
err = b.Mqtt.Subscribe(fanModeSetTopic, func(message string) {
|
err = b.Mqtt.Subscribe(fanModeSetTopic, func(message string) {
|
||||||
fm, err := Str2FanMode(message)
|
fm, err := Str2FanMode(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unknown fan mode %q in message to zone %d", message, zone.ZoneNumber)
|
log.Printf("Unknown fan mode %q in message to zone %d", message, zone.ZoneNumber)
|
||||||
}
|
}
|
||||||
err = zone.SetFanMode(fm)
|
err = zone.setFanMode(fm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot set fan mode to %s in zone %d", message, zone.ZoneNumber)
|
log.Printf("Cannot set fan mode to %s in zone %d", message, zone.ZoneNumber)
|
||||||
}
|
}
|
||||||
@@ -186,21 +175,24 @@ func (b *Bridge) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subscribe to HVAC Mode set topic in MQTT
|
||||||
err = b.Mqtt.Subscribe(hvacModeSetTopic, func(message string) {
|
err = b.Mqtt.Subscribe(hvacModeSetTopic, func(message string) {
|
||||||
|
// If user sets the Home Assistant HVAC mode to off, turn off this zone
|
||||||
if message == HVAC_MODE_OFF {
|
if message == HVAC_MODE_OFF {
|
||||||
err := zone.SetOn(false)
|
err := zone.setOn(false) // turn zone off (REG_ENABLED)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot set zone %d to off", zone.ZoneNumber)
|
log.Printf("Cannot set zone %d to off", zone.ZoneNumber)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Translate HA HVAC mode to Koolnova's
|
||||||
knMode := sys.GetSystemKNMode()
|
knMode := sys.GetSystemKNMode()
|
||||||
knMode = ApplyHvacMode(knMode, message)
|
knMode = ApplyHvacMode(knMode, message)
|
||||||
err = sys.SetSystemKNMode(knMode)
|
err = sys.SetSystemKNMode(knMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot set knmode mode to %x in zone %d", knMode, zone.ZoneNumber)
|
log.Printf("Cannot set knmode mode to %x in zone %d", knMode, zone.ZoneNumber)
|
||||||
}
|
}
|
||||||
err := zone.SetOn(true)
|
err := zone.setOn(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot set zone %d to on", zone.ZoneNumber)
|
log.Printf("Cannot set zone %d to on", zone.ZoneNumber)
|
||||||
return
|
return
|
||||||
@@ -210,7 +202,9 @@ func (b *Bridge) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subscribe to changes in hold mode:
|
||||||
err = b.Mqtt.Subscribe(holdModeSetTopic, func(message string) {
|
err = b.Mqtt.Subscribe(holdModeSetTopic, func(message string) {
|
||||||
|
// Translate HA's hold mode to Koolnova's
|
||||||
knMode := sys.GetSystemKNMode()
|
knMode := sys.GetSystemKNMode()
|
||||||
knMode = ApplyHoldMode(knMode, message)
|
knMode = ApplyHoldMode(knMode, message)
|
||||||
err := sys.SetSystemKNMode(knMode)
|
err := sys.SetSystemKNMode(knMode)
|
||||||
@@ -222,8 +216,9 @@ func (b *Bridge) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define a Home Assistant thermostat
|
||||||
name := fmt.Sprintf("%s_zone%d", b.ModuleName, zone.ZoneNumber)
|
name := fmt.Sprintf("%s_zone%d", b.ModuleName, zone.ZoneNumber)
|
||||||
config := map[string]interface{}{
|
b.publishComponent(HA_COMPONENT_CLIMATE, fmt.Sprintf("zone%d", zone.ZoneNumber), map[string]interface{}{
|
||||||
"name": name,
|
"name": name,
|
||||||
"current_temperature_topic": currentTempTopic,
|
"current_temperature_topic": currentTempTopic,
|
||||||
"precision": 0.1,
|
"precision": 0.1,
|
||||||
@@ -243,62 +238,74 @@ func (b *Bridge) Start() error {
|
|||||||
"hold_modes": []string{HOLD_MODE_UNDERFLOOR_ONLY, HOLD_MODE_FAN_ONLY, HOLD_MODE_UNDERFLOOR_AND_FAN},
|
"hold_modes": []string{HOLD_MODE_UNDERFLOOR_ONLY, HOLD_MODE_FAN_ONLY, HOLD_MODE_UNDERFLOOR_AND_FAN},
|
||||||
"hold_state_topic": holdModeTopic,
|
"hold_state_topic": holdModeTopic,
|
||||||
"hold_command_topic": holdModeSetTopic,
|
"hold_command_topic": holdModeSetTopic,
|
||||||
}
|
})
|
||||||
|
|
||||||
configJSON, _ := json.Marshal(config)
|
// Define a current temperature sensor:
|
||||||
// <discovery_prefix>/<component>/[<node_id>/]<object_id>/config
|
|
||||||
b.Mqtt.Publish(fmt.Sprintf("%s/climate/%s/zone%d/config", b.HassPrefix, b.ModuleName, zone.ZoneNumber), 0, true, string(configJSON))
|
|
||||||
|
|
||||||
// temperature sensor configuration:
|
|
||||||
name = fmt.Sprintf("%s_zone%d_temp", b.ModuleName, zone.ZoneNumber)
|
name = fmt.Sprintf("%s_zone%d_temp", b.ModuleName, zone.ZoneNumber)
|
||||||
config = map[string]interface{}{
|
b.publishComponent(HA_COMPONENT_SENSOR, fmt.Sprintf("zone%d_temp", zone.ZoneNumber), map[string]interface{}{
|
||||||
"name": name,
|
"name": name,
|
||||||
"device_class": "temperature",
|
"device_class": "temperature",
|
||||||
"state_topic": currentTempTopic,
|
"state_topic": currentTempTopic,
|
||||||
"unit_of_measurement": "ºC",
|
"unit_of_measurement": "ºC",
|
||||||
"unique_id": name,
|
"unique_id": name,
|
||||||
}
|
})
|
||||||
|
|
||||||
configJSON, _ = json.Marshal(config)
|
// Define a target temperature sensor:
|
||||||
b.Mqtt.Publish(fmt.Sprintf("%s/sensor/%s/zone%d_temp/config", b.HassPrefix, b.ModuleName, zone.ZoneNumber), 0, true, string(configJSON))
|
name = fmt.Sprintf("%s_zone%d_target_temp", b.ModuleName, zone.ZoneNumber)
|
||||||
|
b.publishComponent(HA_COMPONENT_SENSOR, fmt.Sprintf("zone%d_target_temp", zone.ZoneNumber), map[string]interface{}{
|
||||||
|
"name": name,
|
||||||
|
"device_class": "temperature",
|
||||||
|
"state_topic": targetTempTopic,
|
||||||
|
"unit_of_measurement": "ºC",
|
||||||
|
"unique_id": name,
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Publish changes to system registers:
|
||||||
sys.OnACAirflowChange = func(ac ACMachine) {
|
sys.OnACAirflowChange = func(ac ACMachine) {
|
||||||
airflow := sys.GetAirflow(ac)
|
airflow := sys.GetAirflow(ac)
|
||||||
b.Mqtt.Publish(b.getACTopic(ac, "airflow"), 0, true, strconv.Itoa(airflow))
|
b.Mqtt.Publish(b.getACTopic(ac, "airflow"), 0, true, strconv.Itoa(airflow))
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.OnACTargetTempChange = func(ac ACMachine) {
|
sys.OnACTargetTempChange = func(ac ACMachine) {
|
||||||
targetTemp := sys.GetMachineTargetTemp(ac)
|
targetTemp := sys.GetMachineTargetTemp(ac)
|
||||||
b.Mqtt.Publish(b.getACTopic(ac, "targetTemp"), 0, true, fmt.Sprintf("%g", targetTemp))
|
b.Mqtt.Publish(b.getACTopic(ac, "targetTemp"), 0, true, fmt.Sprintf("%g", targetTemp))
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.OnACTargetFanModeChange = func(ac ACMachine) {
|
sys.OnACTargetFanModeChange = func(ac ACMachine) {
|
||||||
targetAirflow := sys.GetTargetFanMode(ac)
|
targetAirflow := sys.GetTargetFanMode(ac)
|
||||||
b.Mqtt.Publish(b.getACTopic(ac, "fanMode"), 0, true, FanMode2Str(targetAirflow))
|
b.Mqtt.Publish(b.getACTopic(ac, "fanMode"), 0, true, FanMode2Str(targetAirflow))
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.OnEfficiencyChange = func() {
|
sys.OnEfficiencyChange = func() {
|
||||||
efficiency := sys.GetEfficiency()
|
efficiency := sys.GetEfficiency()
|
||||||
b.Mqtt.Publish(b.getSysTopic("efficiency"), 0, true, strconv.Itoa(efficiency))
|
b.Mqtt.Publish(b.getSysTopic("efficiency"), 0, true, strconv.Itoa(efficiency))
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.OnSystemEnabledChange = func() {
|
sys.OnSystemEnabledChange = func() {
|
||||||
enabled := sys.GetSystemEnabled()
|
enabled := sys.GetSystemEnabled()
|
||||||
b.Mqtt.Publish(b.getSysTopic("enabled"), 0, true, fmt.Sprintf("%t", enabled))
|
b.Mqtt.Publish(b.getSysTopic("enabled"), 0, true, fmt.Sprintf("%t", enabled))
|
||||||
publishHvacMode()
|
b.publishHvacMode()
|
||||||
}
|
|
||||||
sys.OnKnModeChange = func() {
|
|
||||||
publishHvacMode()
|
|
||||||
b.Mqtt.Publish(holdModeTopic, 0, true, getHoldMode())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sys.OnKnModeChange = func() {
|
||||||
|
b.publishHvacMode()
|
||||||
|
b.Mqtt.Publish(holdModeTopic, 0, true, sys.HoldMode())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger a callback on all registers so the MQTT broker is updated on connect:
|
||||||
b.zw.TriggerCallbacks()
|
b.zw.TriggerCallbacks()
|
||||||
b.sysw.TriggerCallbacks()
|
b.sysw.TriggerCallbacks()
|
||||||
|
|
||||||
|
// Publish one-off static information
|
||||||
b.Mqtt.Publish(b.getSysTopic("serialBaud"), 0, true, strconv.Itoa(sys.GetBaudRate()))
|
b.Mqtt.Publish(b.getSysTopic("serialBaud"), 0, true, strconv.Itoa(sys.GetBaudRate()))
|
||||||
b.Mqtt.Publish(b.getSysTopic("serialParity"), 0, true, sys.GetParity())
|
b.Mqtt.Publish(b.getSysTopic("serialParity"), 0, true, sys.GetParity())
|
||||||
b.Mqtt.Publish(b.getSysTopic("slaveId"), 0, true, strconv.Itoa(sys.GetSlaveID()))
|
b.Mqtt.Publish(b.getSysTopic("slaveId"), 0, true, strconv.Itoa(sys.GetSlaveID()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// poll polls modbus for changes
|
||||||
func (b *Bridge) poll() error {
|
func (b *Bridge) poll() error {
|
||||||
err := b.zw.Poll()
|
err := b.zw.Poll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -314,13 +321,15 @@ func (b *Bridge) poll() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tick must be invoked periodically to refesh registers from modbus
|
||||||
|
// it also samples temperature and calculates a moving average of read temperatures
|
||||||
func (b *Bridge) Tick() error {
|
func (b *Bridge) Tick() error {
|
||||||
err := b.poll()
|
err := b.poll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, z := range b.zones {
|
for _, z := range b.zones {
|
||||||
z.SampleTemperature()
|
z.sampleTemperature()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -336,3 +345,19 @@ func (b *Bridge) getSysTopic(subtopic string) string {
|
|||||||
func (b *Bridge) getACTopic(ac ACMachine, subtopic string) string {
|
func (b *Bridge) getACTopic(ac ACMachine, subtopic string) string {
|
||||||
return b.getSysTopic(fmt.Sprintf("ac%d/%s", ac, subtopic))
|
return b.getSysTopic(fmt.Sprintf("ac%d/%s", ac, subtopic))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bridge) publishHvacMode() {
|
||||||
|
for _, zone := range b.zones {
|
||||||
|
if zone.isOn() {
|
||||||
|
hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode")
|
||||||
|
mode := b.sys.HVACMode()
|
||||||
|
b.Mqtt.Publish(hvacModeTopic, 0, true, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// publishComponent publishes a Home Assistant component configuration for autodiscovery
|
||||||
|
func (b *Bridge) publishComponent(component, ObjectID string, config map[string]interface{}) {
|
||||||
|
configJSON, _ := json.Marshal(config)
|
||||||
|
b.Mqtt.Publish(fmt.Sprintf("%s/%s/%s/%s/config", b.HassPrefix, component, b.ModuleName, ObjectID), 0, true, string(configJSON))
|
||||||
|
}
|
||||||
|
|||||||
193
kn/bridge_test.go
Normal file
193
kn/bridge_test.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package kn_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"koolnova2mqtt/kn"
|
||||||
|
"koolnova2mqtt/modbus"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/epiclabs-io/ut"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Topic string
|
||||||
|
Payload interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MqttClientMock struct {
|
||||||
|
subscriptions map[string]func(message string)
|
||||||
|
messages []Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMqttClientMock() *MqttClientMock {
|
||||||
|
return &MqttClientMock{
|
||||||
|
subscriptions: make(map[string]func(string)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MqttClientMock) Publish(topic string, qos byte, retained bool, payload string) error {
|
||||||
|
var jsonObject map[string]interface{}
|
||||||
|
var p interface{}
|
||||||
|
err := json.Unmarshal([]byte(payload), &jsonObject)
|
||||||
|
if err == nil {
|
||||||
|
p = jsonObject
|
||||||
|
} else {
|
||||||
|
p = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
m.messages = append(m.messages, Message{
|
||||||
|
Topic: topic,
|
||||||
|
Payload: p,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MqttClientMock) simulateMessage(topic string, payload string) {
|
||||||
|
callback := m.subscriptions[topic]
|
||||||
|
if callback != nil {
|
||||||
|
callback(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MqttClientMock) LastMessage() *Message {
|
||||||
|
if len(m.messages) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &m.messages[len(m.messages)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MqttClientMock) Clear() {
|
||||||
|
m.messages = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MqttClientMock) Subscribe(topic string, callback func(message string)) error {
|
||||||
|
m.subscriptions[topic] = callback
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeys(funcMap map[string]func(string)) []string {
|
||||||
|
keys := make([]string, 0, len(funcMap))
|
||||||
|
for k := range funcMap {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Slice(keys, func(i, j int) bool {
|
||||||
|
return strings.Compare(keys[i], keys[j]) < 0
|
||||||
|
})
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiffValue struct {
|
||||||
|
Address uint16
|
||||||
|
Old uint16
|
||||||
|
New uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestMessage struct {
|
||||||
|
ID int
|
||||||
|
Topic string
|
||||||
|
Payload string
|
||||||
|
Diffs []DiffValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func diffState(old, new []uint16) []DiffValue {
|
||||||
|
if len(old) != len(new) {
|
||||||
|
panic("not the same length")
|
||||||
|
}
|
||||||
|
var diffs []DiffValue
|
||||||
|
for n := 0; n < len(old); n++ {
|
||||||
|
if old[n] != new[n] {
|
||||||
|
diffs = append(diffs, DiffValue{
|
||||||
|
Old: old[n],
|
||||||
|
New: new[n],
|
||||||
|
Address: uint16(n + 1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diffs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBridge(tx *testing.T) {
|
||||||
|
t := ut.BeginTest(tx, false)
|
||||||
|
defer t.FinishTest()
|
||||||
|
var err error
|
||||||
|
|
||||||
|
mqttClient := NewMqttClientMock()
|
||||||
|
modbusClient := modbus.NewMock()
|
||||||
|
b := kn.NewBridge(&kn.Config{
|
||||||
|
ModuleName: "TestModule",
|
||||||
|
SlaveID: 49,
|
||||||
|
TopicPrefix: "topicPrefix",
|
||||||
|
HassPrefix: "hassPrefix",
|
||||||
|
Mqtt: mqttClient,
|
||||||
|
Modbus: modbusClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check the correct subscriptions and messages are sent on connect:
|
||||||
|
err = b.Start()
|
||||||
|
t.Ok(err)
|
||||||
|
t.EqualsFile("subscriptions.json", getKeys(mqttClient.subscriptions))
|
||||||
|
sort.Slice(mqttClient.messages, func(i, j int) bool {
|
||||||
|
return strings.Compare(mqttClient.messages[i].Topic, mqttClient.messages[j].Topic) < 0
|
||||||
|
})
|
||||||
|
t.EqualsFile("connect-messages.json", mqttClient.messages)
|
||||||
|
mqttClient.Clear()
|
||||||
|
|
||||||
|
// Check modbus state is altered when various control messages are received over MQTT
|
||||||
|
|
||||||
|
var messages []TestMessage
|
||||||
|
simulateMessage := func(topic, payload string) {
|
||||||
|
state := append([]uint16(nil), modbusClient.State[49]...)
|
||||||
|
mqttClient.simulateMessage(topic, payload)
|
||||||
|
messages = append(messages, TestMessage{
|
||||||
|
ID: len(messages) + 1,
|
||||||
|
Topic: topic,
|
||||||
|
Payload: payload,
|
||||||
|
Diffs: diffState(state, modbusClient.State[49]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
simulateMessage("topicPrefix/TestModule/sys/holdMode/set", kn.HOLD_MODE_FAN_ONLY)
|
||||||
|
simulateMessage("topicPrefix/TestModule/sys/holdMode/set", kn.HOLD_MODE_UNDERFLOOR_AND_FAN)
|
||||||
|
simulateMessage("topicPrefix/TestModule/sys/holdMode/set", kn.HOLD_MODE_UNDERFLOOR_ONLY)
|
||||||
|
simulateMessage("topicPrefix/TestModule/sys/holdMode/set", "bad mode")
|
||||||
|
|
||||||
|
hvacModes := []string{kn.HVAC_MODE_COOL, kn.HVAC_MODE_HEAT, kn.HVAC_MODE_OFF}
|
||||||
|
holdModes := []string{kn.HOLD_MODE_UNDERFLOOR_ONLY, kn.HOLD_MODE_FAN_ONLY, kn.HOLD_MODE_UNDERFLOOR_AND_FAN}
|
||||||
|
|
||||||
|
for z := 1; z < 11; z++ {
|
||||||
|
simulateMessage(fmt.Sprintf("topicPrefix/TestModule/zone%d/fanMode/set", z), kn.FanMode2Str(kn.FAN_HIGH))
|
||||||
|
simulateMessage(fmt.Sprintf("topicPrefix/TestModule/zone%d/fanMode/set", z), kn.FanMode2Str(kn.FAN_MED))
|
||||||
|
simulateMessage(fmt.Sprintf("topicPrefix/TestModule/zone%d/fanMode/set", z), kn.FanMode2Str(kn.FAN_LOW))
|
||||||
|
simulateMessage(fmt.Sprintf("topicPrefix/TestModule/zone%d/fanMode/set", z), kn.FanMode2Str(kn.FAN_AUTO))
|
||||||
|
simulateMessage(fmt.Sprintf("topicPrefix/TestModule/zone%d/fanMode/set", z), "bad mode")
|
||||||
|
simulateMessage(fmt.Sprintf("topicPrefix/TestModule/zone%d/targetTemp/set", z), strconv.Itoa(20+z))
|
||||||
|
for i := 0; i < len(hvacModes); i++ {
|
||||||
|
simulateMessage(fmt.Sprintf("topicPrefix/TestModule/zone%d/hvacMode/set", z), hvacModes[i])
|
||||||
|
for j := 0; j < len(holdModes); j++ {
|
||||||
|
simulateMessage("topicPrefix/TestModule/sys/holdMode/set", holdModes[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// diffs.json will contain a list of changes. Each item in the array is the result
|
||||||
|
// of each simulateMessage call above.
|
||||||
|
t.EqualsFile("diffs.json", messages)
|
||||||
|
t.EqualsFile("messages.json", mqttClient.messages)
|
||||||
|
|
||||||
|
// simulate changes in temperature by writing random values to temperature registers
|
||||||
|
mqttClient.Clear()
|
||||||
|
n := 0
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
for z := 1; z < 11; z++ {
|
||||||
|
t := (i + 15) * 2
|
||||||
|
n++
|
||||||
|
modbusClient.WriteRegister(49, uint16((z-1)*kn.REG_PER_ZONE+kn.REG_CURRENT_TEMP), uint16(t))
|
||||||
|
}
|
||||||
|
b.Tick()
|
||||||
|
}
|
||||||
|
t.EqualsFile("current-temp.json", mqttClient.messages)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package kn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"koolnova2mqtt/bimap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const NUM_ZONES = 16
|
const NUM_ZONES = 16
|
||||||
@@ -43,22 +42,6 @@ const MODE_UNDERFLOOR_HEATING KnMode = 0x04
|
|||||||
const MODE_UNDERFLOOR_AIR_COOLING KnMode = 0x05
|
const MODE_UNDERFLOOR_AIR_COOLING KnMode = 0x05
|
||||||
const MODE_UNDERFLOOR_AIR_HEATING KnMode = 0x06
|
const MODE_UNDERFLOOR_AIR_HEATING KnMode = 0x06
|
||||||
|
|
||||||
var FanModes = bimap.New(map[interface{}]interface{}{
|
|
||||||
"off": FAN_OFF,
|
|
||||||
"low": FAN_LOW,
|
|
||||||
"medium": FAN_MED,
|
|
||||||
"high": FAN_HIGH,
|
|
||||||
"auto": FAN_AUTO,
|
|
||||||
})
|
|
||||||
|
|
||||||
var KnModes = bimap.New(map[interface{}]interface{}{
|
|
||||||
"air cooling": MODE_AIR_COOLING,
|
|
||||||
"air heating": MODE_AIR_HEATING,
|
|
||||||
"underfloor heating": MODE_UNDERFLOOR_HEATING,
|
|
||||||
"underfloor air cooling": MODE_UNDERFLOOR_AIR_COOLING,
|
|
||||||
"underfloor air heating": MODE_UNDERFLOOR_AIR_HEATING,
|
|
||||||
})
|
|
||||||
|
|
||||||
const HOLD_MODE_UNDERFLOOR_ONLY = "underfloor"
|
const HOLD_MODE_UNDERFLOOR_ONLY = "underfloor"
|
||||||
const HOLD_MODE_FAN_ONLY = "fan"
|
const HOLD_MODE_FAN_ONLY = "fan"
|
||||||
const HOLD_MODE_UNDERFLOOR_AND_FAN = "underfloor and fan"
|
const HOLD_MODE_UNDERFLOOR_AND_FAN = "underfloor and fan"
|
||||||
@@ -76,28 +59,41 @@ const AC2 ACMachine = 2
|
|||||||
const AC3 ACMachine = 3
|
const AC3 ACMachine = 3
|
||||||
const AC4 ACMachine = 4
|
const AC4 ACMachine = 4
|
||||||
|
|
||||||
|
const HA_COMPONENT_SENSOR = "sensor"
|
||||||
|
const HA_COMPONENT_CLIMATE = "climate"
|
||||||
|
|
||||||
func FanMode2Str(fm FanMode) string {
|
func FanMode2Str(fm FanMode) string {
|
||||||
st, ok := FanModes.GetInverse(fm)
|
switch fm {
|
||||||
if !ok {
|
case FAN_OFF:
|
||||||
st = "unknown"
|
return "off"
|
||||||
|
case FAN_LOW:
|
||||||
|
return "low"
|
||||||
|
case FAN_MED:
|
||||||
|
return "medium"
|
||||||
|
case FAN_HIGH:
|
||||||
|
return "high"
|
||||||
|
case FAN_AUTO:
|
||||||
|
return "auto"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
}
|
}
|
||||||
return st.(string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Str2FanMode(st string) (FanMode, error) {
|
func Str2FanMode(st string) (FanMode, error) {
|
||||||
fm, ok := FanModes.Get(st)
|
switch st {
|
||||||
if !ok {
|
case "off":
|
||||||
|
return FAN_OFF, nil
|
||||||
|
case "low":
|
||||||
|
return FAN_LOW, nil
|
||||||
|
case "medium":
|
||||||
|
return FAN_MED, nil
|
||||||
|
case "high":
|
||||||
|
return FAN_HIGH, nil
|
||||||
|
case "auto":
|
||||||
|
return FAN_AUTO, nil
|
||||||
|
default:
|
||||||
return FAN_OFF, errors.New("Unknown fan mode")
|
return FAN_OFF, errors.New("Unknown fan mode")
|
||||||
}
|
}
|
||||||
return fm.(FanMode), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func KnMode2Str(hm KnMode) string {
|
|
||||||
st, ok := KnModes.GetInverse(hm)
|
|
||||||
if !ok {
|
|
||||||
st = "unknown"
|
|
||||||
}
|
|
||||||
return st.(string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApplyHvacMode(knMode KnMode, hvacMode string) KnMode {
|
func ApplyHvacMode(knMode KnMode, hvacMode string) KnMode {
|
||||||
|
|||||||
62
kn/sys.go
62
kn/sys.go
@@ -6,7 +6,10 @@ type SysConfig struct {
|
|||||||
Watcher Watcher
|
Watcher Watcher
|
||||||
}
|
}
|
||||||
|
|
||||||
type Sys struct {
|
// SysDriver watches system registers and allows
|
||||||
|
// to read/change the module configuration
|
||||||
|
// notifies callbacks when specific system registers change
|
||||||
|
type SysDriver struct {
|
||||||
SysConfig
|
SysConfig
|
||||||
OnACAirflowChange func(ac ACMachine)
|
OnACAirflowChange func(ac ACMachine)
|
||||||
OnACTargetTempChange func(ac ACMachine)
|
OnACTargetTempChange func(ac ACMachine)
|
||||||
@@ -18,8 +21,8 @@ type Sys struct {
|
|||||||
|
|
||||||
var ErrUnknownSerialConfig = errors.New("Uknown serial configuration")
|
var ErrUnknownSerialConfig = errors.New("Uknown serial configuration")
|
||||||
|
|
||||||
func NewSys(config *SysConfig) *Sys {
|
func NewSys(config *SysConfig) *SysDriver {
|
||||||
s := &Sys{
|
s := &SysDriver{
|
||||||
SysConfig: *config,
|
SysConfig: *config,
|
||||||
}
|
}
|
||||||
for n := byte(0); n < ACMachines; n++ {
|
for n := byte(0); n < ACMachines; n++ {
|
||||||
@@ -63,31 +66,31 @@ func NewSys(config *SysConfig) *Sys {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) ReadRegister(n int) int {
|
func (s *SysDriver) ReadRegister(n int) int {
|
||||||
r := s.Watcher.ReadRegister(uint16(n))
|
r := s.Watcher.ReadRegister(uint16(n))
|
||||||
return int(r)
|
return int(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) WriteRegister(n int, value uint16) error {
|
func (s *SysDriver) WriteRegister(n int, value uint16) error {
|
||||||
return s.Watcher.WriteRegister(uint16(n), value)
|
return s.Watcher.WriteRegister(uint16(n), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) GetAirflow(ac ACMachine) int {
|
func (s *SysDriver) GetAirflow(ac ACMachine) int {
|
||||||
r := s.ReadRegister(REG_AIRFLOW + int(ac) - 1)
|
r := s.ReadRegister(REG_AIRFLOW + int(ac) - 1)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) GetMachineTargetTemp(ac ACMachine) float32 {
|
func (s *SysDriver) GetMachineTargetTemp(ac ACMachine) float32 {
|
||||||
r := s.ReadRegister(REG_AC_TARGET_TEMP + int(ac) - 1)
|
r := s.ReadRegister(REG_AC_TARGET_TEMP + int(ac) - 1)
|
||||||
return reg2temp(uint16(r))
|
return reg2temp(uint16(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) GetTargetFanMode(ac ACMachine) FanMode {
|
func (s *SysDriver) GetTargetFanMode(ac ACMachine) FanMode {
|
||||||
r := s.ReadRegister(REG_AC_TARGET_FAN_MODE + int(ac) - 1)
|
r := s.ReadRegister(REG_AC_TARGET_FAN_MODE + int(ac) - 1)
|
||||||
return FanMode(r)
|
return FanMode(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) GetBaudRate() int {
|
func (s *SysDriver) GetBaudRate() int {
|
||||||
r := s.ReadRegister(REG_SERIAL_CONFIG)
|
r := s.ReadRegister(REG_SERIAL_CONFIG)
|
||||||
switch r {
|
switch r {
|
||||||
case 2, 6:
|
case 2, 6:
|
||||||
@@ -98,7 +101,7 @@ func (s *Sys) GetBaudRate() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) GetParity() string {
|
func (s *SysDriver) GetParity() string {
|
||||||
r := s.ReadRegister(REG_SERIAL_CONFIG)
|
r := s.ReadRegister(REG_SERIAL_CONFIG)
|
||||||
switch r {
|
switch r {
|
||||||
case 2, 3:
|
case 2, 3:
|
||||||
@@ -109,26 +112,55 @@ func (s *Sys) GetParity() string {
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) GetSlaveID() int {
|
func (s *SysDriver) GetSlaveID() int {
|
||||||
r := s.ReadRegister(REG_SLAVE_ID)
|
r := s.ReadRegister(REG_SLAVE_ID)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) GetEfficiency() int {
|
func (s *SysDriver) GetEfficiency() int {
|
||||||
r := s.ReadRegister(REG_EFFICIENCY)
|
r := s.ReadRegister(REG_EFFICIENCY)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) GetSystemEnabled() bool {
|
func (s *SysDriver) GetSystemEnabled() bool {
|
||||||
r := s.ReadRegister(REG_SYSTEM_ENABLED)
|
r := s.ReadRegister(REG_SYSTEM_ENABLED)
|
||||||
return r != 0
|
return r != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) GetSystemKNMode() KnMode {
|
func (s *SysDriver) GetSystemKNMode() KnMode {
|
||||||
r := s.ReadRegister(REG_SYS_KN_MODE)
|
r := s.ReadRegister(REG_SYS_KN_MODE)
|
||||||
return KnMode(r)
|
return KnMode(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sys) SetSystemKNMode(knMode KnMode) error {
|
func (s *SysDriver) SetSystemKNMode(knMode KnMode) error {
|
||||||
return s.WriteRegister(REG_SYS_KN_MODE, uint16(knMode))
|
return s.WriteRegister(REG_SYS_KN_MODE, uint16(knMode))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HVACMode returns the HA HVAC mode based on the
|
||||||
|
// module state
|
||||||
|
func (s *SysDriver) HVACMode() string {
|
||||||
|
if !s.GetSystemEnabled() {
|
||||||
|
return HVAC_MODE_OFF
|
||||||
|
}
|
||||||
|
switch s.GetSystemKNMode() {
|
||||||
|
case MODE_AIR_COOLING, MODE_UNDERFLOOR_AIR_COOLING:
|
||||||
|
return HVAC_MODE_COOL
|
||||||
|
case MODE_AIR_HEATING, MODE_UNDERFLOOR_HEATING, MODE_UNDERFLOOR_AIR_HEATING:
|
||||||
|
return HVAC_MODE_HEAT
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HoldMode returns the HA Hold Mode based on the
|
||||||
|
// module state
|
||||||
|
func (s *SysDriver) HoldMode() string {
|
||||||
|
switch s.GetSystemKNMode() {
|
||||||
|
case MODE_AIR_COOLING, MODE_AIR_HEATING:
|
||||||
|
return HOLD_MODE_FAN_ONLY
|
||||||
|
case MODE_UNDERFLOOR_HEATING:
|
||||||
|
return HOLD_MODE_UNDERFLOOR_ONLY
|
||||||
|
case MODE_UNDERFLOOR_AIR_COOLING, MODE_UNDERFLOOR_AIR_HEATING:
|
||||||
|
return HOLD_MODE_UNDERFLOOR_AND_FAN
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|||||||
844
kn/testdata/TestBridge/connect-messages.json
vendored
Normal file
844
kn/testdata/TestBridge/connect-messages.json
vendored
Normal file
@@ -0,0 +1,844 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone1/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone1/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone1/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone1/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone1/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone1",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone1/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone1/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone10/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone10/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone10/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone10/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone10/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone10",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone10/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone10/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone2/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone2/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone2/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone2/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone2/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone2",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone2/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone2/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone3/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone3/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone3/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone3/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone3/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone3",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone3/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone3/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone4/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone4/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone4/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone4/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone4/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone4",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone4/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone4/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone5/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone5/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone5/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone5/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone5/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone5",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone5/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone5/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone6/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone6/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone6/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone6/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone6/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone6",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone6/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone6/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone7/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone7/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone7/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone7/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone7/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone7",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone7/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone7/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone8/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone8/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone8/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone8/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone8/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone8",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone8/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone8/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/climate/TestModule/zone9/config",
|
||||||
|
"Payload": {
|
||||||
|
"current_temperature_topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"fan_mode_command_topic": "topicPrefix/TestModule/zone9/fanMode/set",
|
||||||
|
"fan_mode_state_topic": "topicPrefix/TestModule/zone9/fanMode",
|
||||||
|
"fan_modes": [
|
||||||
|
"auto",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high"
|
||||||
|
],
|
||||||
|
"hold_command_topic": "topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"hold_modes": [
|
||||||
|
"underfloor",
|
||||||
|
"fan",
|
||||||
|
"underfloor and fan"
|
||||||
|
],
|
||||||
|
"hold_state_topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"max_temp": 35,
|
||||||
|
"min_temp": 15,
|
||||||
|
"mode_command_topic": "topicPrefix/TestModule/zone9/hvacMode/set",
|
||||||
|
"mode_state_topic": "topicPrefix/TestModule/zone9/hvacMode",
|
||||||
|
"modes": [
|
||||||
|
"cool",
|
||||||
|
"heat",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"name": "TestModule_zone9",
|
||||||
|
"precision": 0.1,
|
||||||
|
"temp_step": 0.5,
|
||||||
|
"temperature_command_topic": "topicPrefix/TestModule/zone9/targetTemp/set",
|
||||||
|
"temperature_state_topic": "topicPrefix/TestModule/zone9/targetTemp",
|
||||||
|
"temperature_unit": "C",
|
||||||
|
"unique_id": "TestModule_zone9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone10_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone10_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone10/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone10_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone10_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone10_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone10_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone1_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone1_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone1/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone1_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone1_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone1_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone1_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone2_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone2_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone2/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone2_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone2_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone2_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone2_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone3_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone3_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone3/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone3_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone3_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone3_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone3_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone4_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone4_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone4/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone4_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone4_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone4_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone4_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone5_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone5_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone5/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone5_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone5_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone5_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone5_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone6_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone6_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone6/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone6_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone6_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone6_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone6_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone7_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone7_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone7/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone7_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone7_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone7_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone7_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone8_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone8_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone8/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone8_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone8_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone8_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone8_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone9_target_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone9_target_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone9/targetTemp",
|
||||||
|
"unique_id": "TestModule_zone9_target_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "hassPrefix/sensor/TestModule/zone9_temp/config",
|
||||||
|
"Payload": {
|
||||||
|
"device_class": "temperature",
|
||||||
|
"name": "TestModule_zone9_temp",
|
||||||
|
"state_topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"unique_id": "TestModule_zone9_temp",
|
||||||
|
"unit_of_measurement": "ºC"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac1/airflow",
|
||||||
|
"Payload": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac1/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac1/targetTemp",
|
||||||
|
"Payload": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac2/airflow",
|
||||||
|
"Payload": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac2/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac2/targetTemp",
|
||||||
|
"Payload": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac3/airflow",
|
||||||
|
"Payload": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac3/fanMode",
|
||||||
|
"Payload": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac3/targetTemp",
|
||||||
|
"Payload": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac4/airflow",
|
||||||
|
"Payload": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac4/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/ac4/targetTemp",
|
||||||
|
"Payload": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/efficiency",
|
||||||
|
"Payload": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/enabled",
|
||||||
|
"Payload": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/holdMode",
|
||||||
|
"Payload": "underfloor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/serialBaud",
|
||||||
|
"Payload": "9600"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/serialParity",
|
||||||
|
"Payload": "even"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/sys/slaveId",
|
||||||
|
"Payload": "49"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/fanMode",
|
||||||
|
"Payload": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/fanMode",
|
||||||
|
"Payload": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/fanMode",
|
||||||
|
"Payload": "auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/hvacMode",
|
||||||
|
"Payload": "heat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/targetTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
}
|
||||||
|
]
|
||||||
802
kn/testdata/TestBridge/current-temp.json
vendored
Normal file
802
kn/testdata/TestBridge/current-temp.json
vendored
Normal file
@@ -0,0 +1,802 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "15.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "16.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "17.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "18"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "18.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "19.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "20.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "21.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "22.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "23.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "24"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone1/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone2/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone3/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone4/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone5/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone6/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone7/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone8/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone9/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Topic": "topicPrefix/TestModule/zone10/currentTemp",
|
||||||
|
"Payload": "24.5"
|
||||||
|
}
|
||||||
|
]
|
||||||
2132
kn/testdata/TestBridge/diffs.json
vendored
Normal file
2132
kn/testdata/TestBridge/diffs.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3278
kn/testdata/TestBridge/messages.json
vendored
Normal file
3278
kn/testdata/TestBridge/messages.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
33
kn/testdata/TestBridge/subscriptions.json
vendored
Normal file
33
kn/testdata/TestBridge/subscriptions.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
[
|
||||||
|
"topicPrefix/TestModule/sys/holdMode/set",
|
||||||
|
"topicPrefix/TestModule/zone1/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone1/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone1/targetTemp/set",
|
||||||
|
"topicPrefix/TestModule/zone10/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone10/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone10/targetTemp/set",
|
||||||
|
"topicPrefix/TestModule/zone2/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone2/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone2/targetTemp/set",
|
||||||
|
"topicPrefix/TestModule/zone3/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone3/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone3/targetTemp/set",
|
||||||
|
"topicPrefix/TestModule/zone4/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone4/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone4/targetTemp/set",
|
||||||
|
"topicPrefix/TestModule/zone5/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone5/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone5/targetTemp/set",
|
||||||
|
"topicPrefix/TestModule/zone6/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone6/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone6/targetTemp/set",
|
||||||
|
"topicPrefix/TestModule/zone7/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone7/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone7/targetTemp/set",
|
||||||
|
"topicPrefix/TestModule/zone8/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone8/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone8/targetTemp/set",
|
||||||
|
"topicPrefix/TestModule/zone9/fanMode/set",
|
||||||
|
"topicPrefix/TestModule/zone9/hvacMode/set",
|
||||||
|
"topicPrefix/TestModule/zone9/targetTemp/set"
|
||||||
|
]
|
||||||
67
kn/zone.go
67
kn/zone.go
@@ -17,6 +17,7 @@ type ZoneConfig struct {
|
|||||||
Watcher Watcher
|
Watcher Watcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zone is a driver to interact with climate zones
|
||||||
type Zone struct {
|
type Zone struct {
|
||||||
ZoneConfig
|
ZoneConfig
|
||||||
OnEnabledChange func()
|
OnEnabledChange func()
|
||||||
@@ -28,27 +29,31 @@ type Zone struct {
|
|||||||
temp *average.MovingAverage
|
temp *average.MovingAverage
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewZone(config *ZoneConfig) *Zone {
|
// newZone creates a new climate zone with the supplied configuration
|
||||||
|
// Invokes callbacks when specific registers change
|
||||||
|
// reads registers to return current state
|
||||||
|
// sets the appropriate registers when set* methods are invoked
|
||||||
|
func newZone(config *ZoneConfig) *Zone {
|
||||||
z := &Zone{
|
z := &Zone{
|
||||||
ZoneConfig: *config,
|
ZoneConfig: *config,
|
||||||
temp: average.New(300),
|
temp: average.New(300),
|
||||||
}
|
}
|
||||||
z.RegisterCallback(REG_ENABLED, func() {
|
z.registerCallback(REG_ENABLED, func() {
|
||||||
if z.OnEnabledChange != nil {
|
if z.OnEnabledChange != nil {
|
||||||
z.OnEnabledChange()
|
z.OnEnabledChange()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
z.RegisterCallback(REG_TARGET_TEMP, func() {
|
z.registerCallback(REG_TARGET_TEMP, func() {
|
||||||
if z.OnTargetTempChange == nil {
|
if z.OnTargetTempChange == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
temp := z.GetTargetTemperature()
|
temp := z.getTargetTemperature()
|
||||||
z.OnTargetTempChange(temp)
|
z.OnTargetTempChange(temp)
|
||||||
})
|
})
|
||||||
z.RegisterCallback(REG_MODE, func() {
|
z.registerCallback(REG_MODE, func() {
|
||||||
fanMode := z.GetFanMode()
|
fanMode := z.getFanMode()
|
||||||
hvacMode := z.GetKnMode()
|
hvacMode := z.getKnMode()
|
||||||
if z.OnFanModeChange != nil {
|
if z.OnFanModeChange != nil {
|
||||||
z.OnFanModeChange(fanMode)
|
z.OnFanModeChange(fanMode)
|
||||||
}
|
}
|
||||||
@@ -59,54 +64,54 @@ func NewZone(config *ZoneConfig) *Zone {
|
|||||||
return z
|
return z
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) RegisterCallback(num int, f func()) {
|
func (z *Zone) registerCallback(num int, f func()) {
|
||||||
z.Watcher.RegisterCallback(uint16((z.ZoneNumber-1)*REG_PER_ZONE+num), func(address uint16) {
|
z.Watcher.RegisterCallback(uint16((z.ZoneNumber-1)*REG_PER_ZONE+num), func(address uint16) {
|
||||||
f()
|
f()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) ReadRegister(num int) uint16 {
|
func (z *Zone) readRegister(num int) uint16 {
|
||||||
return z.Watcher.ReadRegister(uint16((z.ZoneNumber-1)*REG_PER_ZONE + num))
|
return z.Watcher.ReadRegister(uint16((z.ZoneNumber-1)*REG_PER_ZONE + num))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) WriteRegister(num int, value uint16) error {
|
func (z *Zone) writeRegister(num int, value uint16) error {
|
||||||
return z.Watcher.WriteRegister(uint16((z.ZoneNumber-1)*REG_PER_ZONE+num), value)
|
return z.Watcher.WriteRegister(uint16((z.ZoneNumber-1)*REG_PER_ZONE+num), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) IsOn() bool {
|
func (z *Zone) isOn() bool {
|
||||||
r1 := z.ReadRegister(REG_ENABLED)
|
r1 := z.readRegister(REG_ENABLED)
|
||||||
return r1&0x1 != 0
|
return r1&0x1 != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) SetOn(on bool) error {
|
func (z *Zone) setOn(on bool) error {
|
||||||
var r1 uint16
|
var r1 uint16
|
||||||
if on {
|
if on {
|
||||||
r1 = 0x3
|
r1 = 0x3
|
||||||
} else {
|
} else {
|
||||||
r1 = 0x2
|
r1 = 0x2
|
||||||
}
|
}
|
||||||
return z.WriteRegister(REG_ENABLED, r1)
|
return z.writeRegister(REG_ENABLED, r1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) IsPresent() bool {
|
func (z *Zone) isPresent() bool {
|
||||||
r1 := z.ReadRegister(REG_ENABLED)
|
r1 := z.readRegister(REG_ENABLED)
|
||||||
return r1&0x2 != 0
|
return r1&0x2 != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) getCurrentTemperature() float32 {
|
func (z *Zone) getCurrentTemperature() float32 {
|
||||||
r4 := z.ReadRegister(REG_CURRENT_TEMP)
|
r4 := z.readRegister(REG_CURRENT_TEMP)
|
||||||
return reg2temp(r4)
|
return reg2temp(r4)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) GetCurrentTemperature() float32 {
|
func (z *Zone) getAverageCurrentTemperature() float32 {
|
||||||
return float32(math.Round(z.temp.Avg()*10) / 10)
|
return float32(math.Round(z.temp.Avg()*10) / 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) SampleTemperature() {
|
func (z *Zone) sampleTemperature() {
|
||||||
sample := z.getCurrentTemperature()
|
sample := z.getCurrentTemperature()
|
||||||
z.temp.Add(float64(sample))
|
z.temp.Add(float64(sample))
|
||||||
if z.OnCurrentTempChange != nil {
|
if z.OnCurrentTempChange != nil {
|
||||||
t := z.GetCurrentTemperature()
|
t := z.getAverageCurrentTemperature()
|
||||||
if t != z.lastTemp {
|
if t != z.lastTemp {
|
||||||
z.lastTemp = t
|
z.lastTemp = t
|
||||||
z.OnCurrentTempChange(t)
|
z.OnCurrentTempChange(t)
|
||||||
@@ -114,28 +119,28 @@ func (z *Zone) SampleTemperature() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) GetTargetTemperature() float32 {
|
func (z *Zone) getTargetTemperature() float32 {
|
||||||
r3 := z.ReadRegister(REG_TARGET_TEMP)
|
r3 := z.readRegister(REG_TARGET_TEMP)
|
||||||
return reg2temp(r3)
|
return reg2temp(r3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) SetTargetTemperature(targetTemp float32) error {
|
func (z *Zone) setTargetTemperature(targetTemp float32) error {
|
||||||
return z.WriteRegister(REG_TARGET_TEMP, temp2reg(targetTemp))
|
return z.writeRegister(REG_TARGET_TEMP, temp2reg(targetTemp))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) GetFanMode() FanMode {
|
func (z *Zone) getFanMode() FanMode {
|
||||||
r2 := z.ReadRegister(REG_MODE)
|
r2 := z.readRegister(REG_MODE)
|
||||||
return (FanMode)(r2&0x00F0) >> 4
|
return (FanMode)(r2&0x00F0) >> 4
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) SetFanMode(fanMode FanMode) error {
|
func (z *Zone) setFanMode(fanMode FanMode) error {
|
||||||
r2 := z.ReadRegister(REG_MODE) & 0x000F
|
r2 := z.readRegister(REG_MODE) & 0x000F
|
||||||
fm := (uint16(fanMode) & 0x000F) << 4
|
fm := (uint16(fanMode) & 0x000F) << 4
|
||||||
return z.WriteRegister(REG_MODE, r2|fm)
|
return z.writeRegister(REG_MODE, r2|fm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) GetKnMode() KnMode {
|
func (z *Zone) getKnMode() KnMode {
|
||||||
r2 := z.ReadRegister(REG_MODE)
|
r2 := z.readRegister(REG_MODE)
|
||||||
return (KnMode)(r2 & 0x000F)
|
return (KnMode)(r2 & 0x000F)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
8
main.go
8
main.go
@@ -9,7 +9,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewBridges(slaves map[byte]string, templateConfig *kn.Config) []*kn.Bridge {
|
// newBridges builds all bridges from a list of Modbus slaves
|
||||||
|
func newBridges(slaves map[byte]string, templateConfig *kn.Config) []*kn.Bridge {
|
||||||
var bridges []*kn.Bridge
|
var bridges []*kn.Bridge
|
||||||
for id, name := range slaves {
|
for id, name := range slaves {
|
||||||
config := *templateConfig
|
config := *templateConfig
|
||||||
@@ -23,9 +24,11 @@ func NewBridges(slaves map[byte]string, templateConfig *kn.Config) []*kn.Bridge
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
// configure CTRL+C as a way to stop the application
|
||||||
ctrlC := make(chan os.Signal, 1)
|
ctrlC := make(chan os.Signal, 1)
|
||||||
signal.Notify(ctrlC, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(ctrlC, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
// read configuration from the command line
|
||||||
config := ParseCommandLine()
|
config := ParseCommandLine()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -35,7 +38,7 @@ func main() {
|
|||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
newSessionID := config.MqttClient.ID
|
newSessionID := config.MqttClient.ID
|
||||||
if sessionID != newSessionID {
|
if sessionID != newSessionID {
|
||||||
bridges = NewBridges(config.slaves, config.BridgeTemplateConfig)
|
bridges = newBridges(config.slaves, config.BridgeTemplateConfig)
|
||||||
for _, b := range bridges {
|
for _, b := range bridges {
|
||||||
err := b.Start()
|
err := b.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -56,5 +59,6 @@ func main() {
|
|||||||
<-ctrlC
|
<-ctrlC
|
||||||
|
|
||||||
config.MqttClient.Close()
|
config.MqttClient.Close()
|
||||||
|
config.BridgeTemplateConfig.Modbus.Close()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ type Mock struct {
|
|||||||
State map[byte][]uint16
|
State map[byte][]uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMock() Modbus {
|
func NewMock() *Mock {
|
||||||
return &Mock{
|
return &Mock{
|
||||||
State: map[byte][]uint16{
|
State: map[byte][]uint16{
|
||||||
49: {3, 68, 41, 41, 3, 68, 41, 41, 3, 68, 41, 45, 3, 68, 41, 45, 3, 68, 41, 42, 3, 52, 41, 40, 3, 52, 41, 44, 3, 68, 41, 41, 3, 68, 41, 40, 3, 68, 41, 41, 0, 68, 0, 0, 0, 68, 0, 0, 0, 68, 0, 0, 0, 68, 0, 0, 0, 68, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 3, 4, 2, 49, 3, 7, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
49: {3, 68, 41, 41, 3, 68, 41, 41, 3, 68, 41, 45, 3, 68, 41, 45, 3, 68, 41, 42, 3, 52, 41, 40, 3, 52, 41, 44, 3, 68, 41, 41, 3, 68, 41, 40, 3, 68, 41, 41, 0, 68, 0, 0, 0, 68, 0, 0, 0, 68, 0, 0, 0, 68, 0, 0, 0, 68, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 3, 4, 2, 49, 3, 7, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
|||||||
@@ -10,12 +10,6 @@ import (
|
|||||||
gmodbus "github.com/wz2b/modbus"
|
gmodbus "github.com/wz2b/modbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Modbus interface {
|
|
||||||
ReadRegister(slaveID byte, address uint16, quantity uint16) (results []uint16, err error)
|
|
||||||
WriteRegister(slaveID byte, address uint16, value uint16) (results []uint16, err error)
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Port string
|
Port string
|
||||||
BaudRate int
|
BaudRate int
|
||||||
@@ -25,7 +19,7 @@ type Config struct {
|
|||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type modbus struct {
|
type Modbus struct {
|
||||||
handler *gmodbus.RTUClientHandler
|
handler *gmodbus.RTUClientHandler
|
||||||
client gmodbus.Client
|
client gmodbus.Client
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
@@ -37,7 +31,7 @@ func throttle(ms int) {
|
|||||||
|
|
||||||
var ErrIncorrectResultSize = errors.New("Incorrect number of results returned")
|
var ErrIncorrectResultSize = errors.New("Incorrect number of results returned")
|
||||||
|
|
||||||
func New(config *Config) (Modbus, error) {
|
func New(config *Config) (*Modbus, error) {
|
||||||
handler := gmodbus.NewRTUClientHandler(config.Port)
|
handler := gmodbus.NewRTUClientHandler(config.Port)
|
||||||
handler.BaudRate = config.BaudRate
|
handler.BaudRate = config.BaudRate
|
||||||
handler.DataBits = config.DataBits
|
handler.DataBits = config.DataBits
|
||||||
@@ -45,13 +39,13 @@ func New(config *Config) (Modbus, error) {
|
|||||||
handler.StopBits = config.StopBits
|
handler.StopBits = config.StopBits
|
||||||
handler.Timeout = config.Timeout
|
handler.Timeout = config.Timeout
|
||||||
|
|
||||||
return &modbus{
|
return &Modbus{
|
||||||
handler: handler,
|
handler: handler,
|
||||||
client: gmodbus.NewClient(handler),
|
client: gmodbus.NewClient(handler),
|
||||||
}, handler.Connect()
|
}, handler.Connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mb *modbus) Close() error {
|
func (mb *Modbus) Close() error {
|
||||||
return mb.handler.Close()
|
return mb.handler.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +60,7 @@ func parseResults(r []byte, quantity uint16) ([]uint16, error) {
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mb *modbus) ReadRegister(slaveID byte, address uint16, quantity uint16) (results []uint16, err error) {
|
func (mb *Modbus) ReadRegister(slaveID byte, address uint16, quantity uint16) (results []uint16, err error) {
|
||||||
err = mb.try(slaveID, func() (err error) {
|
err = mb.try(slaveID, func() (err error) {
|
||||||
r, err := mb.client.ReadHoldingRegisters(address-1, quantity)
|
r, err := mb.client.ReadHoldingRegisters(address-1, quantity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -78,7 +72,7 @@ func (mb *modbus) ReadRegister(slaveID byte, address uint16, quantity uint16) (r
|
|||||||
return results, err
|
return results, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mb *modbus) WriteRegister(slaveID byte, address uint16, value uint16) (results []uint16, err error) {
|
func (mb *Modbus) WriteRegister(slaveID byte, address uint16, value uint16) (results []uint16, err error) {
|
||||||
err = mb.try(slaveID, func() (err error) {
|
err = mb.try(slaveID, func() (err error) {
|
||||||
r, err := mb.client.WriteSingleRegister(address-1, value)
|
r, err := mb.client.WriteSingleRegister(address-1, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -90,7 +84,7 @@ func (mb *modbus) WriteRegister(slaveID byte, address uint16, value uint16) (res
|
|||||||
return results, err
|
return results, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mb *modbus) try(slaveID byte, f func() error) (err error) {
|
func (mb *Modbus) try(slaveID byte, f func() error) (err error) {
|
||||||
mb.lock.Lock()
|
mb.lock.Lock()
|
||||||
defer mb.lock.Unlock()
|
defer mb.lock.Unlock()
|
||||||
defer throttle(100)
|
defer throttle(100)
|
||||||
|
|||||||
@@ -4,16 +4,21 @@ package watcher
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"koolnova2mqtt/modbus"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Modbus interface {
|
||||||
|
ReadRegister(slaveID byte, address uint16, quantity uint16) (results []uint16, err error)
|
||||||
|
WriteRegister(slaveID byte, address uint16, value uint16) (results []uint16, err error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
// Config contains the configuration parameters for a new Watcher instance
|
// Config contains the configuration parameters for a new Watcher instance
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Address uint16 // Start address
|
Address uint16 // Start address
|
||||||
Quantity uint16 // Number of registers to watch
|
Quantity uint16 // Number of registers to watch
|
||||||
SlaveID byte // SlaveID to watch
|
SlaveID byte // SlaveID to watch
|
||||||
Modbus modbus.Modbus // Modbus interface
|
Modbus Modbus // Modbus interface
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watcher represents a cache of modbus registers in a device
|
// Watcher represents a cache of modbus registers in a device
|
||||||
|
|||||||
Reference in New Issue
Block a user