Files
koolnova2mqtt/kn/bridge_test.go
2020-12-22 01:22:00 +01:00

194 lines
5.2 KiB
Go

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)
}