mirror of
https://github.com/jpeletier/koolnova2mqtt.git
synced 2026-07-16 16:26:57 +00:00
map hvac /hold modes. use modbus module
This commit is contained in:
+58
-14
@@ -3,6 +3,7 @@ package kn
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"koolnova2mqtt/modbus"
|
||||||
"koolnova2mqtt/watcher"
|
"koolnova2mqtt/watcher"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -12,14 +13,13 @@ type Publish func(topic string, qos byte, retained bool, payload string)
|
|||||||
type Subscribe func(topic string, callback func(message string)) error
|
type Subscribe func(topic string, callback func(message string)) error
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ModuleName string
|
ModuleName string
|
||||||
SlaveID byte
|
SlaveID byte
|
||||||
Publish Publish
|
Publish Publish
|
||||||
Subscribe Subscribe
|
Subscribe Subscribe
|
||||||
TopicPrefix string
|
TopicPrefix string
|
||||||
HassPrefix string
|
HassPrefix string
|
||||||
ReadRegister watcher.ReadRegister
|
Modbus modbus.Modbus
|
||||||
WriteRegister watcher.WriteRegister
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
@@ -54,8 +54,7 @@ func NewBridge(config *Config) *Bridge {
|
|||||||
Quantity: TOTAL_ZONE_REGISTERS,
|
Quantity: TOTAL_ZONE_REGISTERS,
|
||||||
RegisterSize: 2,
|
RegisterSize: 2,
|
||||||
SlaveID: config.SlaveID,
|
SlaveID: config.SlaveID,
|
||||||
Read: config.ReadRegister,
|
Modbus: config.Modbus,
|
||||||
Write: config.WriteRegister,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
sysw := watcher.New(&watcher.Config{
|
sysw := watcher.New(&watcher.Config{
|
||||||
@@ -63,8 +62,7 @@ func NewBridge(config *Config) *Bridge {
|
|||||||
Quantity: TOTAL_SYS_REGISTERS,
|
Quantity: TOTAL_SYS_REGISTERS,
|
||||||
RegisterSize: 2,
|
RegisterSize: 2,
|
||||||
SlaveID: config.SlaveID,
|
SlaveID: config.SlaveID,
|
||||||
Read: config.ReadRegister,
|
Modbus: config.Modbus,
|
||||||
Write: config.WriteRegister,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
b := &Bridge{
|
b := &Bridge{
|
||||||
@@ -146,8 +144,18 @@ func (b *Bridge) Start() error {
|
|||||||
fanModeTopic := b.getZoneTopic(zone.ZoneNumber, "fanMode")
|
fanModeTopic := b.getZoneTopic(zone.ZoneNumber, "fanMode")
|
||||||
fanModeSetTopic := fanModeTopic + "/set"
|
fanModeSetTopic := fanModeTopic + "/set"
|
||||||
hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode")
|
hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode")
|
||||||
hvacModeTopicSet := hvacModeTopic + "/set"
|
hvacModeSetTopic := hvacModeTopic + "/set"
|
||||||
|
|
||||||
|
zone.OnEnabledChange = func() {
|
||||||
|
hvacModeTopic := b.getZoneTopic(zone.ZoneNumber, "hvacMode")
|
||||||
|
var mode string
|
||||||
|
if zone.IsOn() {
|
||||||
|
mode = getHVACMode()
|
||||||
|
} else {
|
||||||
|
mode = HVAC_MODE_OFF
|
||||||
|
}
|
||||||
|
b.Publish(hvacModeTopic, 0, true, mode)
|
||||||
|
}
|
||||||
zone.OnCurrentTempChange = func(currentTemp float32) {
|
zone.OnCurrentTempChange = func(currentTemp float32) {
|
||||||
b.Publish(currentTempTopic, 0, true, fmt.Sprintf("%g", currentTemp))
|
b.Publish(currentTempTopic, 0, true, fmt.Sprintf("%g", currentTemp))
|
||||||
}
|
}
|
||||||
@@ -190,6 +198,42 @@ func (b *Bridge) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = b.Subscribe(hvacModeSetTopic, func(message string) {
|
||||||
|
if message == HVAC_MODE_OFF {
|
||||||
|
err := zone.SetOn(false)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Cannot set zone %d to off", zone.ZoneNumber)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := zone.SetOn(true)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Cannot set zone %d to on", zone.ZoneNumber)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = b.Subscribe(holdModeSetTopic, func(message string) {
|
||||||
|
knMode := sys.GetSystemKNMode()
|
||||||
|
knMode = ApplyHoldMode(knMode, message)
|
||||||
|
err := sys.SetSystemKNMode(knMode)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Cannot set knmode mode to %x in zone %d", knMode, zone.ZoneNumber)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
name := fmt.Sprintf("%s_zone%d", b.ModuleName, zone.ZoneNumber)
|
name := fmt.Sprintf("%s_zone%d", b.ModuleName, zone.ZoneNumber)
|
||||||
config := map[string]interface{}{
|
config := map[string]interface{}{
|
||||||
"name": name,
|
"name": name,
|
||||||
@@ -204,7 +248,7 @@ func (b *Bridge) Start() error {
|
|||||||
"max_temp": 35,
|
"max_temp": 35,
|
||||||
"modes": []string{HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF},
|
"modes": []string{HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF},
|
||||||
"mode_state_topic": hvacModeTopic,
|
"mode_state_topic": hvacModeTopic,
|
||||||
"mode_command_topic": hvacModeTopicSet,
|
"mode_command_topic": hvacModeSetTopic,
|
||||||
"fan_modes": []string{"auto", "low", "medium", "high"},
|
"fan_modes": []string{"auto", "low", "medium", "high"},
|
||||||
"fan_mode_state_topic": fanModeTopic,
|
"fan_mode_state_topic": fanModeTopic,
|
||||||
"fan_mode_command_topic": fanModeSetTopic,
|
"fan_mode_command_topic": fanModeSetTopic,
|
||||||
|
|||||||
@@ -99,3 +99,47 @@ func KnMode2Str(hm KnMode) string {
|
|||||||
}
|
}
|
||||||
return st.(string)
|
return st.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApplyHvacMode(knMode KnMode, hvacMode string) KnMode {
|
||||||
|
switch knMode {
|
||||||
|
case MODE_AIR_COOLING:
|
||||||
|
if hvacMode == HVAC_MODE_HEAT {
|
||||||
|
return MODE_AIR_HEATING
|
||||||
|
}
|
||||||
|
case MODE_AIR_HEATING:
|
||||||
|
if hvacMode == HVAC_MODE_COOL {
|
||||||
|
return MODE_AIR_COOLING
|
||||||
|
}
|
||||||
|
case MODE_UNDERFLOOR_AIR_COOLING:
|
||||||
|
if hvacMode == HVAC_MODE_HEAT {
|
||||||
|
return MODE_UNDERFLOOR_AIR_HEATING
|
||||||
|
}
|
||||||
|
case MODE_UNDERFLOOR_AIR_HEATING, MODE_UNDERFLOOR_HEATING:
|
||||||
|
if hvacMode == HVAC_MODE_COOL {
|
||||||
|
return MODE_UNDERFLOOR_AIR_COOLING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return knMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApplyHoldMode(knMode KnMode, holdMode string) KnMode {
|
||||||
|
cool := knMode == MODE_AIR_COOLING || knMode == MODE_UNDERFLOOR_AIR_COOLING
|
||||||
|
switch holdMode {
|
||||||
|
case HOLD_MODE_FAN_ONLY:
|
||||||
|
if cool {
|
||||||
|
return MODE_AIR_COOLING
|
||||||
|
}
|
||||||
|
return MODE_AIR_HEATING
|
||||||
|
case HOLD_MODE_UNDERFLOOR_ONLY:
|
||||||
|
if cool {
|
||||||
|
return MODE_UNDERFLOOR_AIR_COOLING
|
||||||
|
}
|
||||||
|
return MODE_UNDERFLOOR_HEATING
|
||||||
|
case HOLD_MODE_UNDERFLOOR_AND_FAN:
|
||||||
|
if cool {
|
||||||
|
return MODE_UNDERFLOOR_AIR_COOLING
|
||||||
|
}
|
||||||
|
return MODE_UNDERFLOOR_AIR_HEATING
|
||||||
|
}
|
||||||
|
return knMode
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ func (s *Sys) ReadRegister(n int) int {
|
|||||||
return int(r[1])
|
return int(r[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Sys) WriteRegister(n int, value uint16) error {
|
||||||
|
return s.Watcher.WriteRegister(uint16(n), value)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Sys) GetAirflow(ac ACMachine) int {
|
func (s *Sys) GetAirflow(ac ACMachine) int {
|
||||||
r := s.ReadRegister(REG_AIRFLOW + int(ac) - 1)
|
r := s.ReadRegister(REG_AIRFLOW + int(ac) - 1)
|
||||||
return r
|
return r
|
||||||
@@ -124,3 +128,7 @@ func (s *Sys) GetSystemKNMode() KnMode {
|
|||||||
r := s.ReadRegister(REG_SYS_KN_MODE)
|
r := s.ReadRegister(REG_SYS_KN_MODE)
|
||||||
return KnMode(r)
|
return KnMode(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Sys) SetSystemKNMode(knMode KnMode) error {
|
||||||
|
return s.WriteRegister(REG_SYS_KN_MODE, uint16(knMode))
|
||||||
|
}
|
||||||
|
|||||||
+18
-2
@@ -17,6 +17,7 @@ type ZoneConfig struct {
|
|||||||
|
|
||||||
type Zone struct {
|
type Zone struct {
|
||||||
ZoneConfig
|
ZoneConfig
|
||||||
|
OnEnabledChange func()
|
||||||
OnCurrentTempChange func(newTemp float32)
|
OnCurrentTempChange func(newTemp float32)
|
||||||
OnTargetTempChange func(newTemp float32)
|
OnTargetTempChange func(newTemp float32)
|
||||||
OnFanModeChange func(newMode FanMode)
|
OnFanModeChange func(newMode FanMode)
|
||||||
@@ -27,6 +28,11 @@ func NewZone(config *ZoneConfig) *Zone {
|
|||||||
z := &Zone{
|
z := &Zone{
|
||||||
ZoneConfig: *config,
|
ZoneConfig: *config,
|
||||||
}
|
}
|
||||||
|
z.RegisterCallback(REG_ENABLED, func() {
|
||||||
|
if z.OnEnabledChange != nil {
|
||||||
|
z.OnEnabledChange()
|
||||||
|
}
|
||||||
|
})
|
||||||
z.RegisterCallback(REG_CURRENT_TEMP, func() {
|
z.RegisterCallback(REG_CURRENT_TEMP, func() {
|
||||||
if z.OnCurrentTempChange == nil {
|
if z.OnCurrentTempChange == nil {
|
||||||
return
|
return
|
||||||
@@ -45,7 +51,7 @@ func NewZone(config *ZoneConfig) *Zone {
|
|||||||
})
|
})
|
||||||
z.RegisterCallback(REG_MODE, func() {
|
z.RegisterCallback(REG_MODE, func() {
|
||||||
fanMode := z.GetFanMode()
|
fanMode := z.GetFanMode()
|
||||||
hvacMode := z.GetHvacMode()
|
hvacMode := z.GetKnMode()
|
||||||
if z.OnFanModeChange != nil {
|
if z.OnFanModeChange != nil {
|
||||||
z.OnFanModeChange(fanMode)
|
z.OnFanModeChange(fanMode)
|
||||||
}
|
}
|
||||||
@@ -76,6 +82,16 @@ func (z *Zone) IsOn() bool {
|
|||||||
return r1&0x1 != 0
|
return r1&0x1 != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (z *Zone) SetOn(on bool) error {
|
||||||
|
var r1 uint16
|
||||||
|
if on {
|
||||||
|
r1 = 0x3
|
||||||
|
} else {
|
||||||
|
r1 = 0x2
|
||||||
|
}
|
||||||
|
return z.WriteRegister(REG_ENABLED, r1)
|
||||||
|
}
|
||||||
|
|
||||||
func (z *Zone) IsPresent() bool {
|
func (z *Zone) IsPresent() bool {
|
||||||
r1 := z.ReadRegister(REG_ENABLED)
|
r1 := z.ReadRegister(REG_ENABLED)
|
||||||
return r1&0x2 != 0
|
return r1&0x2 != 0
|
||||||
@@ -106,7 +122,7 @@ func (z *Zone) SetFanMode(fanMode FanMode) error {
|
|||||||
return z.WriteRegister(REG_MODE, r2|fm)
|
return z.WriteRegister(REG_MODE, r2|fm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z *Zone) GetHvacMode() KnMode {
|
func (z *Zone) GetKnMode() KnMode {
|
||||||
r2 := z.ReadRegister(REG_MODE)
|
r2 := z.ReadRegister(REG_MODE)
|
||||||
return (KnMode)(r2 & 0x000F)
|
return (KnMode)(r2 & 0x000F)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,41 +6,19 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"koolnova2mqtt/kn"
|
"koolnova2mqtt/kn"
|
||||||
"koolnova2mqtt/watcher"
|
"koolnova2mqtt/modbus"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
MQTT "github.com/eclipse/paho.mqtt.golang"
|
MQTT "github.com/eclipse/paho.mqtt.golang"
|
||||||
"github.com/goburrow/modbus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildReader(handler *modbus.RTUClientHandler, client modbus.Client, lock *sync.RWMutex) watcher.ReadRegister {
|
|
||||||
return func(slaveID byte, address uint16, quantity uint16) (results []byte, err error) {
|
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
handler.SlaveId = slaveID
|
|
||||||
results, err = client.ReadHoldingRegisters(address-1, quantity)
|
|
||||||
return results, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildWriter(handler *modbus.RTUClientHandler, client modbus.Client, lock *sync.RWMutex) watcher.WriteRegister {
|
|
||||||
return func(slaveID byte, address uint16, value uint16) (results []byte, err error) {
|
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
handler.SlaveId = slaveID
|
|
||||||
results, err = client.WriteSingleRegister(address-1, value)
|
|
||||||
return results, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateNodeName(slaveID string, port string) string {
|
func generateNodeName(slaveID string, port string) string {
|
||||||
reg, err := regexp.Compile("[^a-zA-Z0-9]+")
|
reg, err := regexp.Compile("[^a-zA-Z0-9]+")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -79,24 +57,18 @@ func main() {
|
|||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
handler := modbus.NewRTUClientHandler(*modbusPort)
|
mb, err := modbus.New(&modbus.Config{
|
||||||
handler.BaudRate = *modbusPortBaudRate
|
Port: *modbusPort,
|
||||||
handler.DataBits = *modbusDataBits
|
BaudRate: *modbusPortBaudRate,
|
||||||
handler.Parity = *modbusPortParity
|
DataBits: *modbusDataBits,
|
||||||
handler.StopBits = *modbusStopBits
|
Parity: *modbusPortParity,
|
||||||
handler.Timeout = 5 * time.Second
|
StopBits: *modbusStopBits,
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
err := handler.Connect()
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error connecting modbus: %s", err)
|
log.Fatalf("Error initializing modbus: %s", err)
|
||||||
}
|
}
|
||||||
defer handler.Close()
|
defer mb.Close()
|
||||||
|
|
||||||
modbusClient := modbus.NewClient(handler)
|
|
||||||
|
|
||||||
lock := &sync.RWMutex{}
|
|
||||||
registerReader := buildReader(handler, modbusClient, lock)
|
|
||||||
registerWriter := buildWriter(handler, modbusClient, lock)
|
|
||||||
|
|
||||||
var mqttClient MQTT.Client
|
var mqttClient MQTT.Client
|
||||||
publish := func(topic string, qos byte, retained bool, payload string) {
|
publish := func(topic string, qos byte, retained bool, payload string) {
|
||||||
@@ -147,14 +119,13 @@ func main() {
|
|||||||
log.Fatalf("Error parsing slaveID list")
|
log.Fatalf("Error parsing slaveID list")
|
||||||
}
|
}
|
||||||
bridge := kn.NewBridge(&kn.Config{
|
bridge := kn.NewBridge(&kn.Config{
|
||||||
ModuleName: slaveName,
|
ModuleName: slaveName,
|
||||||
SlaveID: byte(slaveID),
|
SlaveID: byte(slaveID),
|
||||||
Publish: publish,
|
Publish: publish,
|
||||||
Subscribe: subscribe,
|
Subscribe: subscribe,
|
||||||
TopicPrefix: *prefix,
|
TopicPrefix: *prefix,
|
||||||
HassPrefix: *hassPrefix,
|
HassPrefix: *hassPrefix,
|
||||||
ReadRegister: registerReader,
|
Modbus: mb,
|
||||||
WriteRegister: registerWriter,
|
|
||||||
})
|
})
|
||||||
bridges = append(bridges, bridge)
|
bridges = append(bridges, bridge)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-7
@@ -3,18 +3,15 @@ package watcher
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"koolnova2mqtt/modbus"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReadRegister func(slaveID byte, address uint16, quantity uint16) (results []byte, err error)
|
|
||||||
type WriteRegister func(slaveID byte, address uint16, value uint16) (results []byte, err error)
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Address uint16
|
Address uint16
|
||||||
Quantity uint16
|
Quantity uint16
|
||||||
SlaveID byte
|
SlaveID byte
|
||||||
Read ReadRegister
|
Modbus modbus.Modbus
|
||||||
Write WriteRegister
|
|
||||||
RegisterSize int
|
RegisterSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +40,7 @@ func (w *Watcher) RegisterCallback(address uint16, callback func(address uint16)
|
|||||||
|
|
||||||
func (w *Watcher) Poll() error {
|
func (w *Watcher) Poll() error {
|
||||||
w.lock.Lock()
|
w.lock.Lock()
|
||||||
newState, err := w.Read(w.SlaveID, w.Address, w.Quantity)
|
newState, err := w.Modbus.ReadRegister(w.SlaveID, w.Address, w.Quantity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.lock.Unlock()
|
w.lock.Unlock()
|
||||||
return err
|
return err
|
||||||
@@ -100,7 +97,7 @@ func (w *Watcher) ReadRegister(address uint16) (value []byte) {
|
|||||||
|
|
||||||
func (w *Watcher) WriteRegister(address uint16, value uint16) error {
|
func (w *Watcher) WriteRegister(address uint16, value uint16) error {
|
||||||
w.lock.Lock()
|
w.lock.Lock()
|
||||||
results, err := w.Write(w.SlaveID, address, value)
|
results, err := w.Modbus.WriteRegister(w.SlaveID, address, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.lock.Unlock()
|
w.lock.Unlock()
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user