diff --git a/bimap/bimap.go b/bimap/bimap.go deleted file mode 100644 index 513d0f7..0000000 --- a/bimap/bimap.go +++ /dev/null @@ -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() -} diff --git a/bimap/bimap_test.go b/bimap/bimap_test.go deleted file mode 100644 index 46069c5..0000000 --- a/bimap/bimap_test.go +++ /dev/null @@ -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) - -} diff --git a/kn/bridge.go b/kn/bridge.go index ced8112..1c67943 100644 --- a/kn/bridge.go +++ b/kn/bridge.go @@ -3,43 +3,46 @@ package kn import ( "encoding/json" "fmt" - "koolnova2mqtt/modbus" "koolnova2mqtt/watcher" "log" "strconv" ) +// MqttClient defines the expected MQTT client pub sub interface type MqttClient interface { Publish(topic string, qos byte, retained bool, payload string) error Subscribe(topic string, callback func(message string)) error } +// Config defines de Modbus<>MQTT bridge configuration type Config struct { - ModuleName string - SlaveID byte - Mqtt MqttClient - TopicPrefix string - HassPrefix string - Modbus modbus.Modbus + ModuleName string // name of the module the modbus interface is connected to + SlaveID byte // SlaveID of the module in the bus + TopicPrefix string // MQTT topic prefix to publish information + HassPrefix string // Home Assistant sensor discovery prefix + Mqtt MqttClient // MQTT client + Modbus watcher.Modbus // Modbus client } +// Bridge bridges Modbus and MQTT protocols type Bridge struct { - Config - zw *watcher.Watcher - sysw *watcher.Watcher - refresh func() - zones []*Zone + Config // embedded configuration + zw *watcher.Watcher // watcher to detect register changes in zones + sysw *watcher.Watcher // watcher to detect register changes in system registers + zones []*Zone // List of present zones in this module + sys *SysDriver } +// getActiveZones returns the list of active zones in this module func getActiveZones(w Watcher) ([]*Zone, error) { var zones []*Zone for n := 0; n < NUM_ZONES; n++ { - zone := NewZone(&ZoneConfig{ + zone := newZone(&ZoneConfig{ ZoneNumber: n + 1, Watcher: w, }) - isPresent := zone.IsPresent() + isPresent := zone.isPresent() if isPresent { zones = append(zones, zone) } @@ -47,6 +50,7 @@ func getActiveZones(w Watcher) ([]*Zone, error) { return zones, nil } +// NewBridge returns a new Modbus<>MQTT bridge func NewBridge(config *Config) *Bridge { b := &Bridge{ Config: *config, @@ -54,8 +58,11 @@ func NewBridge(config *Config) *Bridge { 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 { + // Define a watcher to watch the zone registers zw := watcher.New(&watcher.Config{ Address: FIRST_ZONE_REGISTER, Quantity: TOTAL_ZONE_REGISTERS, @@ -63,17 +70,20 @@ func (b *Bridge) Start() error { Modbus: b.Modbus, }) + // Define a watcher to watch the system registers sysw := watcher.New(&watcher.Config{ Address: FIRST_SYS_REGISTER, Quantity: TOTAL_SYS_REGISTERS, SlaveID: b.SlaveID, Modbus: b.Modbus, }) + b.zw = zw b.sysw = sysw sys := NewSys(&SysConfig{ Watcher: b.sysw, }) + b.sys = sys log.Printf("Starting bridge for %s\n", b.ModuleName) err := b.poll() @@ -81,49 +91,18 @@ func (b *Bridge) Start() error { return err } + // Get Active zones zones, err := getActiveZones(b.zw) b.zones = zones 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) - 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") holdModeSetTopic := holdModeTopic + "/set" + // configure publishing when modbus registers change for _, zone := range zones { zone := zone currentTempTopic := b.getZoneTopic(zone.ZoneNumber, "currentTemp") @@ -134,36 +113,45 @@ func (b *Bridge) Start() error { hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode") 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() { hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode") var mode string - if zone.IsOn() { - mode = getHVACMode() + if zone.isOn() { + mode = sys.HVACMode() } else { mode = HVAC_MODE_OFF } b.Mqtt.Publish(hvacModeTopic, 0, true, mode) } + + // if the current temperature changes, forward value to the + // correspondig MQTT topic zone.OnCurrentTempChange = func(currentTemp float32) { 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) { b.Mqtt.Publish(targetTempTopic, 0, true, fmt.Sprintf("%g", targetTemp)) } + + // Publish changes to the fan mode zone.OnFanModeChange = func(fanMode 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) { targetTemp, err := strconv.ParseFloat(message, 32) if err != nil { log.Printf("Error parsing targetTemperature in topic %s: %s", targetTempSetTopic, err) return } - err = zone.SetTargetTemperature(float32(targetTemp)) + err = zone.setTargetTemperature(float32(targetTemp)) if err != nil { 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 } + // Subscribe to fan mode set topic in MQTT err = b.Mqtt.Subscribe(fanModeSetTopic, func(message string) { fm, err := Str2FanMode(message) if err != nil { 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 { 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 } + // Subscribe to HVAC Mode set topic in MQTT 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 { - err := zone.SetOn(false) + err := zone.setOn(false) // turn zone off (REG_ENABLED) if err != nil { log.Printf("Cannot set zone %d to off", zone.ZoneNumber) } return } + // Translate HA HVAC mode to Koolnova's knMode := sys.GetSystemKNMode() knMode = ApplyHvacMode(knMode, message) err = sys.SetSystemKNMode(knMode) if err != nil { 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 { log.Printf("Cannot set zone %d to on", zone.ZoneNumber) return @@ -210,7 +202,9 @@ func (b *Bridge) Start() error { return err } + // Subscribe to changes in hold mode: err = b.Mqtt.Subscribe(holdModeSetTopic, func(message string) { + // Translate HA's hold mode to Koolnova's knMode := sys.GetSystemKNMode() knMode = ApplyHoldMode(knMode, message) err := sys.SetSystemKNMode(knMode) @@ -222,8 +216,9 @@ func (b *Bridge) Start() error { return err } + // Define a Home Assistant thermostat 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, "current_temperature_topic": currentTempTopic, "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_state_topic": holdModeTopic, "hold_command_topic": holdModeSetTopic, - } + }) - configJSON, _ := json.Marshal(config) - // //[/]/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: + // Define a current temperature sensor: 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, "device_class": "temperature", "state_topic": currentTempTopic, "unit_of_measurement": "ºC", "unique_id": name, - } + }) - configJSON, _ = json.Marshal(config) - b.Mqtt.Publish(fmt.Sprintf("%s/sensor/%s/zone%d_temp/config", b.HassPrefix, b.ModuleName, zone.ZoneNumber), 0, true, string(configJSON)) + // Define a target temperature sensor: + 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) { airflow := sys.GetAirflow(ac) b.Mqtt.Publish(b.getACTopic(ac, "airflow"), 0, true, strconv.Itoa(airflow)) } + sys.OnACTargetTempChange = func(ac ACMachine) { targetTemp := sys.GetMachineTargetTemp(ac) b.Mqtt.Publish(b.getACTopic(ac, "targetTemp"), 0, true, fmt.Sprintf("%g", targetTemp)) } + sys.OnACTargetFanModeChange = func(ac ACMachine) { targetAirflow := sys.GetTargetFanMode(ac) b.Mqtt.Publish(b.getACTopic(ac, "fanMode"), 0, true, FanMode2Str(targetAirflow)) } + sys.OnEfficiencyChange = func() { efficiency := sys.GetEfficiency() b.Mqtt.Publish(b.getSysTopic("efficiency"), 0, true, strconv.Itoa(efficiency)) } + sys.OnSystemEnabledChange = func() { enabled := sys.GetSystemEnabled() b.Mqtt.Publish(b.getSysTopic("enabled"), 0, true, fmt.Sprintf("%t", enabled)) - publishHvacMode() - } - sys.OnKnModeChange = func() { - publishHvacMode() - b.Mqtt.Publish(holdModeTopic, 0, true, getHoldMode()) + b.publishHvacMode() } + 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.sysw.TriggerCallbacks() + // Publish one-off static information 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("slaveId"), 0, true, strconv.Itoa(sys.GetSlaveID())) return nil } +// poll polls modbus for changes func (b *Bridge) poll() error { err := b.zw.Poll() if err != nil { @@ -314,13 +321,15 @@ func (b *Bridge) poll() error { 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 { err := b.poll() if err != nil { return err } for _, z := range b.zones { - z.SampleTemperature() + z.sampleTemperature() } return nil } @@ -336,3 +345,19 @@ func (b *Bridge) getSysTopic(subtopic string) string { func (b *Bridge) getACTopic(ac ACMachine, subtopic string) string { 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)) +} diff --git a/kn/bridge_test.go b/kn/bridge_test.go new file mode 100644 index 0000000..197370e --- /dev/null +++ b/kn/bridge_test.go @@ -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) + +} diff --git a/kn/constants.go b/kn/constants.go index 28006dd..c5d35a2 100644 --- a/kn/constants.go +++ b/kn/constants.go @@ -2,7 +2,6 @@ package kn import ( "errors" - "koolnova2mqtt/bimap" ) 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_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_FAN_ONLY = "fan" const HOLD_MODE_UNDERFLOOR_AND_FAN = "underfloor and fan" @@ -76,28 +59,41 @@ const AC2 ACMachine = 2 const AC3 ACMachine = 3 const AC4 ACMachine = 4 +const HA_COMPONENT_SENSOR = "sensor" +const HA_COMPONENT_CLIMATE = "climate" + func FanMode2Str(fm FanMode) string { - st, ok := FanModes.GetInverse(fm) - if !ok { - st = "unknown" + switch fm { + case FAN_OFF: + 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) { - fm, ok := FanModes.Get(st) - if !ok { + switch st { + 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 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 { diff --git a/kn/sys.go b/kn/sys.go index 9519ca1..cffb99f 100644 --- a/kn/sys.go +++ b/kn/sys.go @@ -6,7 +6,10 @@ type SysConfig struct { 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 OnACAirflowChange func(ac ACMachine) OnACTargetTempChange func(ac ACMachine) @@ -18,8 +21,8 @@ type Sys struct { var ErrUnknownSerialConfig = errors.New("Uknown serial configuration") -func NewSys(config *SysConfig) *Sys { - s := &Sys{ +func NewSys(config *SysConfig) *SysDriver { + s := &SysDriver{ SysConfig: *config, } for n := byte(0); n < ACMachines; n++ { @@ -63,31 +66,31 @@ func NewSys(config *SysConfig) *Sys { return s } -func (s *Sys) ReadRegister(n int) int { +func (s *SysDriver) ReadRegister(n int) int { r := s.Watcher.ReadRegister(uint16(n)) 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) } -func (s *Sys) GetAirflow(ac ACMachine) int { +func (s *SysDriver) GetAirflow(ac ACMachine) int { r := s.ReadRegister(REG_AIRFLOW + int(ac) - 1) 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) 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) return FanMode(r) } -func (s *Sys) GetBaudRate() int { +func (s *SysDriver) GetBaudRate() int { r := s.ReadRegister(REG_SERIAL_CONFIG) switch r { case 2, 6: @@ -98,7 +101,7 @@ func (s *Sys) GetBaudRate() int { return 0 } -func (s *Sys) GetParity() string { +func (s *SysDriver) GetParity() string { r := s.ReadRegister(REG_SERIAL_CONFIG) switch r { case 2, 3: @@ -109,26 +112,55 @@ func (s *Sys) GetParity() string { return "unknown" } -func (s *Sys) GetSlaveID() int { +func (s *SysDriver) GetSlaveID() int { r := s.ReadRegister(REG_SLAVE_ID) return r } -func (s *Sys) GetEfficiency() int { +func (s *SysDriver) GetEfficiency() int { r := s.ReadRegister(REG_EFFICIENCY) return r } -func (s *Sys) GetSystemEnabled() bool { +func (s *SysDriver) GetSystemEnabled() bool { r := s.ReadRegister(REG_SYSTEM_ENABLED) return r != 0 } -func (s *Sys) GetSystemKNMode() KnMode { +func (s *SysDriver) GetSystemKNMode() KnMode { r := s.ReadRegister(REG_SYS_KN_MODE) 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)) } + +// 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" +} diff --git a/kn/testdata/TestBridge/connect-messages.json b/kn/testdata/TestBridge/connect-messages.json new file mode 100644 index 0000000..d198d2e --- /dev/null +++ b/kn/testdata/TestBridge/connect-messages.json @@ -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" + } +] \ No newline at end of file diff --git a/kn/testdata/TestBridge/current-temp.json b/kn/testdata/TestBridge/current-temp.json new file mode 100644 index 0000000..3fb260e --- /dev/null +++ b/kn/testdata/TestBridge/current-temp.json @@ -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" + } +] \ No newline at end of file diff --git a/kn/testdata/TestBridge/diffs.json b/kn/testdata/TestBridge/diffs.json new file mode 100644 index 0000000..a21ebd7 --- /dev/null +++ b/kn/testdata/TestBridge/diffs.json @@ -0,0 +1,2132 @@ +[ + { + "ID": 1, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 2, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 3, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 4, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "bad mode", + "Diffs": null + }, + { + "ID": 5, + "Topic": "topicPrefix/TestModule/zone1/fanMode/set", + "Payload": "high", + "Diffs": [ + { + "Address": 2, + "Old": 68, + "New": 52 + } + ] + }, + { + "ID": 6, + "Topic": "topicPrefix/TestModule/zone1/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 2, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 7, + "Topic": "topicPrefix/TestModule/zone1/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 2, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 8, + "Topic": "topicPrefix/TestModule/zone1/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 2, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 9, + "Topic": "topicPrefix/TestModule/zone1/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 2, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 10, + "Topic": "topicPrefix/TestModule/zone1/targetTemp/set", + "Payload": "21", + "Diffs": [ + { + "Address": 3, + "Old": 41, + "New": 42 + } + ] + }, + { + "ID": 11, + "Topic": "topicPrefix/TestModule/zone1/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 5 + } + ] + }, + { + "ID": 12, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 13, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 14, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 15, + "Topic": "topicPrefix/TestModule/zone1/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 16, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 17, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 18, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 19, + "Topic": "topicPrefix/TestModule/zone1/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 1, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 20, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 21, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 22, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 23, + "Topic": "topicPrefix/TestModule/zone2/fanMode/set", + "Payload": "high", + "Diffs": [ + { + "Address": 6, + "Old": 68, + "New": 52 + } + ] + }, + { + "ID": 24, + "Topic": "topicPrefix/TestModule/zone2/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 6, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 25, + "Topic": "topicPrefix/TestModule/zone2/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 6, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 26, + "Topic": "topicPrefix/TestModule/zone2/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 6, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 27, + "Topic": "topicPrefix/TestModule/zone2/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 6, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 28, + "Topic": "topicPrefix/TestModule/zone2/targetTemp/set", + "Payload": "22", + "Diffs": [ + { + "Address": 7, + "Old": 41, + "New": 44 + } + ] + }, + { + "ID": 29, + "Topic": "topicPrefix/TestModule/zone2/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 5 + } + ] + }, + { + "ID": 30, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 31, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 32, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 33, + "Topic": "topicPrefix/TestModule/zone2/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 34, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 35, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 36, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 37, + "Topic": "topicPrefix/TestModule/zone2/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 5, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 38, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 39, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 40, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 41, + "Topic": "topicPrefix/TestModule/zone3/fanMode/set", + "Payload": "high", + "Diffs": [ + { + "Address": 10, + "Old": 68, + "New": 52 + } + ] + }, + { + "ID": 42, + "Topic": "topicPrefix/TestModule/zone3/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 10, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 43, + "Topic": "topicPrefix/TestModule/zone3/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 10, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 44, + "Topic": "topicPrefix/TestModule/zone3/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 10, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 45, + "Topic": "topicPrefix/TestModule/zone3/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 10, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 46, + "Topic": "topicPrefix/TestModule/zone3/targetTemp/set", + "Payload": "23", + "Diffs": [ + { + "Address": 11, + "Old": 41, + "New": 46 + } + ] + }, + { + "ID": 47, + "Topic": "topicPrefix/TestModule/zone3/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 5 + } + ] + }, + { + "ID": 48, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 49, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 50, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 51, + "Topic": "topicPrefix/TestModule/zone3/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 52, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 53, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 54, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 55, + "Topic": "topicPrefix/TestModule/zone3/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 9, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 56, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 57, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 58, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 59, + "Topic": "topicPrefix/TestModule/zone4/fanMode/set", + "Payload": "high", + "Diffs": [ + { + "Address": 14, + "Old": 68, + "New": 52 + } + ] + }, + { + "ID": 60, + "Topic": "topicPrefix/TestModule/zone4/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 14, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 61, + "Topic": "topicPrefix/TestModule/zone4/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 14, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 62, + "Topic": "topicPrefix/TestModule/zone4/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 14, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 63, + "Topic": "topicPrefix/TestModule/zone4/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 14, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 64, + "Topic": "topicPrefix/TestModule/zone4/targetTemp/set", + "Payload": "24", + "Diffs": [ + { + "Address": 15, + "Old": 41, + "New": 48 + } + ] + }, + { + "ID": 65, + "Topic": "topicPrefix/TestModule/zone4/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 5 + } + ] + }, + { + "ID": 66, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 67, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 68, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 69, + "Topic": "topicPrefix/TestModule/zone4/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 70, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 71, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 72, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 73, + "Topic": "topicPrefix/TestModule/zone4/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 13, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 74, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 75, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 76, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 77, + "Topic": "topicPrefix/TestModule/zone5/fanMode/set", + "Payload": "high", + "Diffs": [ + { + "Address": 18, + "Old": 68, + "New": 52 + } + ] + }, + { + "ID": 78, + "Topic": "topicPrefix/TestModule/zone5/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 18, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 79, + "Topic": "topicPrefix/TestModule/zone5/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 18, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 80, + "Topic": "topicPrefix/TestModule/zone5/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 18, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 81, + "Topic": "topicPrefix/TestModule/zone5/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 18, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 82, + "Topic": "topicPrefix/TestModule/zone5/targetTemp/set", + "Payload": "25", + "Diffs": [ + { + "Address": 19, + "Old": 41, + "New": 50 + } + ] + }, + { + "ID": 83, + "Topic": "topicPrefix/TestModule/zone5/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 5 + } + ] + }, + { + "ID": 84, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 85, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 86, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 87, + "Topic": "topicPrefix/TestModule/zone5/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 88, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 89, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 90, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 91, + "Topic": "topicPrefix/TestModule/zone5/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 17, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 92, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 93, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 94, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 95, + "Topic": "topicPrefix/TestModule/zone6/fanMode/set", + "Payload": "high", + "Diffs": null + }, + { + "ID": 96, + "Topic": "topicPrefix/TestModule/zone6/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 22, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 97, + "Topic": "topicPrefix/TestModule/zone6/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 22, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 98, + "Topic": "topicPrefix/TestModule/zone6/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 22, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 99, + "Topic": "topicPrefix/TestModule/zone6/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 22, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 100, + "Topic": "topicPrefix/TestModule/zone6/targetTemp/set", + "Payload": "26", + "Diffs": [ + { + "Address": 23, + "Old": 41, + "New": 52 + } + ] + }, + { + "ID": 101, + "Topic": "topicPrefix/TestModule/zone6/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 5 + } + ] + }, + { + "ID": 102, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 103, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 104, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 105, + "Topic": "topicPrefix/TestModule/zone6/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 106, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 107, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 108, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 109, + "Topic": "topicPrefix/TestModule/zone6/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 21, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 110, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 111, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 112, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 113, + "Topic": "topicPrefix/TestModule/zone7/fanMode/set", + "Payload": "high", + "Diffs": null + }, + { + "ID": 114, + "Topic": "topicPrefix/TestModule/zone7/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 26, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 115, + "Topic": "topicPrefix/TestModule/zone7/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 26, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 116, + "Topic": "topicPrefix/TestModule/zone7/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 26, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 117, + "Topic": "topicPrefix/TestModule/zone7/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 26, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 118, + "Topic": "topicPrefix/TestModule/zone7/targetTemp/set", + "Payload": "27", + "Diffs": [ + { + "Address": 27, + "Old": 41, + "New": 54 + } + ] + }, + { + "ID": 119, + "Topic": "topicPrefix/TestModule/zone7/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 5 + } + ] + }, + { + "ID": 120, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 121, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 122, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 123, + "Topic": "topicPrefix/TestModule/zone7/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 124, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 125, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 126, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 127, + "Topic": "topicPrefix/TestModule/zone7/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 25, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 128, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 129, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 130, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 131, + "Topic": "topicPrefix/TestModule/zone8/fanMode/set", + "Payload": "high", + "Diffs": [ + { + "Address": 30, + "Old": 68, + "New": 52 + } + ] + }, + { + "ID": 132, + "Topic": "topicPrefix/TestModule/zone8/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 30, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 133, + "Topic": "topicPrefix/TestModule/zone8/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 30, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 134, + "Topic": "topicPrefix/TestModule/zone8/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 30, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 135, + "Topic": "topicPrefix/TestModule/zone8/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 30, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 136, + "Topic": "topicPrefix/TestModule/zone8/targetTemp/set", + "Payload": "28", + "Diffs": [ + { + "Address": 31, + "Old": 41, + "New": 56 + } + ] + }, + { + "ID": 137, + "Topic": "topicPrefix/TestModule/zone8/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 5 + } + ] + }, + { + "ID": 138, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 139, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 140, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 141, + "Topic": "topicPrefix/TestModule/zone8/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 142, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 143, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 144, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 145, + "Topic": "topicPrefix/TestModule/zone8/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 29, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 146, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 147, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 148, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 149, + "Topic": "topicPrefix/TestModule/zone9/fanMode/set", + "Payload": "high", + "Diffs": [ + { + "Address": 34, + "Old": 68, + "New": 52 + } + ] + }, + { + "ID": 150, + "Topic": "topicPrefix/TestModule/zone9/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 34, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 151, + "Topic": "topicPrefix/TestModule/zone9/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 34, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 152, + "Topic": "topicPrefix/TestModule/zone9/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 34, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 153, + "Topic": "topicPrefix/TestModule/zone9/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 34, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 154, + "Topic": "topicPrefix/TestModule/zone9/targetTemp/set", + "Payload": "29", + "Diffs": [ + { + "Address": 35, + "Old": 41, + "New": 58 + } + ] + }, + { + "ID": 155, + "Topic": "topicPrefix/TestModule/zone9/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 5 + } + ] + }, + { + "ID": 156, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 157, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 158, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 159, + "Topic": "topicPrefix/TestModule/zone9/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 160, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 161, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 162, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 163, + "Topic": "topicPrefix/TestModule/zone9/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 33, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 164, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 165, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 166, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 167, + "Topic": "topicPrefix/TestModule/zone10/fanMode/set", + "Payload": "high", + "Diffs": [ + { + "Address": 38, + "Old": 68, + "New": 52 + } + ] + }, + { + "ID": 168, + "Topic": "topicPrefix/TestModule/zone10/fanMode/set", + "Payload": "medium", + "Diffs": [ + { + "Address": 38, + "Old": 52, + "New": 36 + } + ] + }, + { + "ID": 169, + "Topic": "topicPrefix/TestModule/zone10/fanMode/set", + "Payload": "low", + "Diffs": [ + { + "Address": 38, + "Old": 36, + "New": 20 + } + ] + }, + { + "ID": 170, + "Topic": "topicPrefix/TestModule/zone10/fanMode/set", + "Payload": "auto", + "Diffs": [ + { + "Address": 38, + "Old": 20, + "New": 68 + } + ] + }, + { + "ID": 171, + "Topic": "topicPrefix/TestModule/zone10/fanMode/set", + "Payload": "bad mode", + "Diffs": [ + { + "Address": 38, + "Old": 68, + "New": 4 + } + ] + }, + { + "ID": 172, + "Topic": "topicPrefix/TestModule/zone10/targetTemp/set", + "Payload": "30", + "Diffs": [ + { + "Address": 39, + "Old": 41, + "New": 60 + } + ] + }, + { + "ID": 173, + "Topic": "topicPrefix/TestModule/zone10/hvacMode/set", + "Payload": "cool", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 5 + } + ] + }, + { + "ID": 174, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": null + }, + { + "ID": 175, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 1 + } + ] + }, + { + "ID": 176, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 1, + "New": 5 + } + ] + }, + { + "ID": 177, + "Topic": "topicPrefix/TestModule/zone10/hvacMode/set", + "Payload": "heat", + "Diffs": [ + { + "Address": 82, + "Old": 5, + "New": 6 + } + ] + }, + { + "ID": 178, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 179, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 180, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + }, + { + "ID": 181, + "Topic": "topicPrefix/TestModule/zone10/hvacMode/set", + "Payload": "off", + "Diffs": [ + { + "Address": 37, + "Old": 3, + "New": 2 + } + ] + }, + { + "ID": 182, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor", + "Diffs": [ + { + "Address": 82, + "Old": 6, + "New": 4 + } + ] + }, + { + "ID": 183, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "fan", + "Diffs": [ + { + "Address": 82, + "Old": 4, + "New": 2 + } + ] + }, + { + "ID": 184, + "Topic": "topicPrefix/TestModule/sys/holdMode/set", + "Payload": "underfloor and fan", + "Diffs": [ + { + "Address": 82, + "Old": 2, + "New": 6 + } + ] + } +] \ No newline at end of file diff --git a/kn/testdata/TestBridge/messages.json b/kn/testdata/TestBridge/messages.json new file mode 100644 index 0000000..7576dcb --- /dev/null +++ b/kn/testdata/TestBridge/messages.json @@ -0,0 +1,3278 @@ +[ + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone1/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone1/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone1/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone1/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone1/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone1/targetTemp", + "Payload": "21" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone1/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone2/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone2/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone2/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone2/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone2/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone2/targetTemp", + "Payload": "22" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone2/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone3/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone3/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone3/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone3/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone3/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone3/targetTemp", + "Payload": "23" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone3/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone4/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone4/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone4/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone4/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone4/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone4/targetTemp", + "Payload": "24" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone4/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone5/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone5/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone5/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone5/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone5/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone5/targetTemp", + "Payload": "25" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone5/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone6/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone6/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone6/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone6/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone6/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone6/targetTemp", + "Payload": "26" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone6/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone7/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone7/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone7/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone7/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone7/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone7/targetTemp", + "Payload": "27" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone7/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone8/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone8/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone8/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone8/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone8/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone8/targetTemp", + "Payload": "28" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone8/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone9/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone9/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone9/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone9/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone9/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone9/targetTemp", + "Payload": "29" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone9/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone10/fanMode", + "Payload": "high" + }, + { + "Topic": "topicPrefix/TestModule/zone10/fanMode", + "Payload": "medium" + }, + { + "Topic": "topicPrefix/TestModule/zone10/fanMode", + "Payload": "low" + }, + { + "Topic": "topicPrefix/TestModule/zone10/fanMode", + "Payload": "auto" + }, + { + "Topic": "topicPrefix/TestModule/zone10/fanMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/zone10/targetTemp", + "Payload": "30" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "cool" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "heat" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + }, + { + "Topic": "topicPrefix/TestModule/zone10/hvacMode", + "Payload": "off" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "fan" + }, + { + "Topic": "topicPrefix/TestModule/sys/holdMode", + "Payload": "underfloor and fan" + } +] \ No newline at end of file diff --git a/kn/testdata/TestBridge/subscriptions.json b/kn/testdata/TestBridge/subscriptions.json new file mode 100644 index 0000000..05a0052 --- /dev/null +++ b/kn/testdata/TestBridge/subscriptions.json @@ -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" +] \ No newline at end of file diff --git a/kn/zone.go b/kn/zone.go index 39bfaee..2f000c8 100644 --- a/kn/zone.go +++ b/kn/zone.go @@ -17,6 +17,7 @@ type ZoneConfig struct { Watcher Watcher } +// Zone is a driver to interact with climate zones type Zone struct { ZoneConfig OnEnabledChange func() @@ -28,27 +29,31 @@ type Zone struct { 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{ ZoneConfig: *config, temp: average.New(300), } - z.RegisterCallback(REG_ENABLED, func() { + z.registerCallback(REG_ENABLED, func() { if z.OnEnabledChange != nil { z.OnEnabledChange() } }) - z.RegisterCallback(REG_TARGET_TEMP, func() { + z.registerCallback(REG_TARGET_TEMP, func() { if z.OnTargetTempChange == nil { return } - temp := z.GetTargetTemperature() + temp := z.getTargetTemperature() z.OnTargetTempChange(temp) }) - z.RegisterCallback(REG_MODE, func() { - fanMode := z.GetFanMode() - hvacMode := z.GetKnMode() + z.registerCallback(REG_MODE, func() { + fanMode := z.getFanMode() + hvacMode := z.getKnMode() if z.OnFanModeChange != nil { z.OnFanModeChange(fanMode) } @@ -59,54 +64,54 @@ func NewZone(config *ZoneConfig) *Zone { 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) { 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)) } -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) } -func (z *Zone) IsOn() bool { - r1 := z.ReadRegister(REG_ENABLED) +func (z *Zone) isOn() bool { + r1 := z.readRegister(REG_ENABLED) return r1&0x1 != 0 } -func (z *Zone) SetOn(on bool) error { +func (z *Zone) setOn(on bool) error { var r1 uint16 if on { r1 = 0x3 } else { r1 = 0x2 } - return z.WriteRegister(REG_ENABLED, r1) + return z.writeRegister(REG_ENABLED, r1) } -func (z *Zone) IsPresent() bool { - r1 := z.ReadRegister(REG_ENABLED) +func (z *Zone) isPresent() bool { + r1 := z.readRegister(REG_ENABLED) return r1&0x2 != 0 } func (z *Zone) getCurrentTemperature() float32 { - r4 := z.ReadRegister(REG_CURRENT_TEMP) + r4 := z.readRegister(REG_CURRENT_TEMP) return reg2temp(r4) } -func (z *Zone) GetCurrentTemperature() float32 { +func (z *Zone) getAverageCurrentTemperature() float32 { return float32(math.Round(z.temp.Avg()*10) / 10) } -func (z *Zone) SampleTemperature() { +func (z *Zone) sampleTemperature() { sample := z.getCurrentTemperature() z.temp.Add(float64(sample)) if z.OnCurrentTempChange != nil { - t := z.GetCurrentTemperature() + t := z.getAverageCurrentTemperature() if t != z.lastTemp { z.lastTemp = t z.OnCurrentTempChange(t) @@ -114,28 +119,28 @@ func (z *Zone) SampleTemperature() { } } -func (z *Zone) GetTargetTemperature() float32 { - r3 := z.ReadRegister(REG_TARGET_TEMP) +func (z *Zone) getTargetTemperature() float32 { + r3 := z.readRegister(REG_TARGET_TEMP) return reg2temp(r3) } -func (z *Zone) SetTargetTemperature(targetTemp float32) error { - return z.WriteRegister(REG_TARGET_TEMP, temp2reg(targetTemp)) +func (z *Zone) setTargetTemperature(targetTemp float32) error { + return z.writeRegister(REG_TARGET_TEMP, temp2reg(targetTemp)) } -func (z *Zone) GetFanMode() FanMode { - r2 := z.ReadRegister(REG_MODE) +func (z *Zone) getFanMode() FanMode { + r2 := z.readRegister(REG_MODE) return (FanMode)(r2&0x00F0) >> 4 } -func (z *Zone) SetFanMode(fanMode FanMode) error { - r2 := z.ReadRegister(REG_MODE) & 0x000F +func (z *Zone) setFanMode(fanMode FanMode) error { + r2 := z.readRegister(REG_MODE) & 0x000F fm := (uint16(fanMode) & 0x000F) << 4 - return z.WriteRegister(REG_MODE, r2|fm) + return z.writeRegister(REG_MODE, r2|fm) } -func (z *Zone) GetKnMode() KnMode { - r2 := z.ReadRegister(REG_MODE) +func (z *Zone) getKnMode() KnMode { + r2 := z.readRegister(REG_MODE) return (KnMode)(r2 & 0x000F) } diff --git a/main.go b/main.go index ff83326..0adc411 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,8 @@ import ( "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 for id, name := range slaves { config := *templateConfig @@ -23,9 +24,11 @@ func NewBridges(slaves map[byte]string, templateConfig *kn.Config) []*kn.Bridge func main() { + // configure CTRL+C as a way to stop the application ctrlC := make(chan os.Signal, 1) signal.Notify(ctrlC, os.Interrupt, syscall.SIGTERM) + // read configuration from the command line config := ParseCommandLine() go func() { @@ -35,7 +38,7 @@ func main() { for range ticker.C { newSessionID := config.MqttClient.ID if sessionID != newSessionID { - bridges = NewBridges(config.slaves, config.BridgeTemplateConfig) + bridges = newBridges(config.slaves, config.BridgeTemplateConfig) for _, b := range bridges { err := b.Start() if err != nil { @@ -56,5 +59,6 @@ func main() { <-ctrlC config.MqttClient.Close() + config.BridgeTemplateConfig.Modbus.Close() } diff --git a/modbus/mock.go b/modbus/mock.go index 991f48c..b4ab878 100644 --- a/modbus/mock.go +++ b/modbus/mock.go @@ -8,7 +8,7 @@ type Mock struct { State map[byte][]uint16 } -func NewMock() Modbus { +func NewMock() *Mock { return &Mock{ 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}, diff --git a/modbus/modbus.go b/modbus/modbus.go index 1011325..78a3c9f 100644 --- a/modbus/modbus.go +++ b/modbus/modbus.go @@ -10,12 +10,6 @@ import ( 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 { Port string BaudRate int @@ -25,7 +19,7 @@ type Config struct { Timeout time.Duration } -type modbus struct { +type Modbus struct { handler *gmodbus.RTUClientHandler client gmodbus.Client lock sync.RWMutex @@ -37,7 +31,7 @@ func throttle(ms int) { 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.BaudRate = config.BaudRate handler.DataBits = config.DataBits @@ -45,13 +39,13 @@ func New(config *Config) (Modbus, error) { handler.StopBits = config.StopBits handler.Timeout = config.Timeout - return &modbus{ + return &Modbus{ handler: handler, client: gmodbus.NewClient(handler), }, handler.Connect() } -func (mb *modbus) Close() error { +func (mb *Modbus) Close() error { return mb.handler.Close() } @@ -66,7 +60,7 @@ func parseResults(r []byte, quantity uint16) ([]uint16, error) { 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) { r, err := mb.client.ReadHoldingRegisters(address-1, quantity) if err != nil { @@ -78,7 +72,7 @@ func (mb *modbus) ReadRegister(slaveID byte, address uint16, quantity uint16) (r 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) { r, err := mb.client.WriteSingleRegister(address-1, value) if err != nil { @@ -90,7 +84,7 @@ func (mb *modbus) WriteRegister(slaveID byte, address uint16, value uint16) (res 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() defer mb.lock.Unlock() defer throttle(100) diff --git a/watcher/watcher.go b/watcher/watcher.go index 5e55e0f..e7afeca 100644 --- a/watcher/watcher.go +++ b/watcher/watcher.go @@ -4,16 +4,21 @@ package watcher import ( "errors" - "koolnova2mqtt/modbus" "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 type Config struct { - Address uint16 // Start address - Quantity uint16 // Number of registers to watch - SlaveID byte // SlaveID to watch - Modbus modbus.Modbus // Modbus interface + Address uint16 // Start address + Quantity uint16 // Number of registers to watch + SlaveID byte // SlaveID to watch + Modbus Modbus // Modbus interface } // Watcher represents a cache of modbus registers in a device