From 3c09b2f047762036382094d60b2ee8e0cd9719ed Mon Sep 17 00:00:00 2001 From: Pekka Vainio Date: Fri, 29 Oct 2021 21:35:40 +0300 Subject: [PATCH] Support changing ventilation speed --- README.md | 9 ++++-- go.mod | 2 +- vallox.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++---- vallox_test.go | 17 +++++++++++ 4 files changed, 101 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3ebf735..1eb8b76 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,20 @@ This module implements Vallox RS485 serial protocol. Currently it can: - read temperature reported by device: outside -> incoming -> inside -> outgoing - read ventilation fan speed +- change ventilation fan speed ## Supported devices The module has been tested with only one device so far: -- Vallox Digit SE model 3500 SE made in 2001 +- Vallox Digit SE model 3500 SE made in 2001 (one with old led panel, no lcd panel) Probably it will support other Vallox models using RS485 serial bus for remote controllers. It is possible that some registers have have changed through time since this device register does not match Vallox documentation. -Use at your own risk! Vallox documentation especially warns that using incorrect registers or incorrect values. +Use at your own risk! Vallox documentation warns about using incorrect registers or incorrect values may damage the device. -## Usage +## Usage + +To write registers (speed) Config.EnableWrite need to be set to true. ## Example diff --git a/go.mod b/go.mod index 2264398..9026529 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/pvainio/vallox +module github.com/pvainio/vallox-rs485 go 1.17 diff --git a/vallox.go b/vallox.go index 451deea..39b22e1 100644 --- a/vallox.go +++ b/vallox.go @@ -1,3 +1,4 @@ +// Package valloxrs485 implements Vallox RS485 protocol package valloxrs485 import ( @@ -6,14 +7,23 @@ import ( "encoding/binary" "fmt" "io" + "io/ioutil" + "log" "time" "github.com/tarm/serial" ) +// Config foo type Config struct { - Device string + // Device file for rs485 device + Device string + // RemoteClientId is the id for this device in Vallox rs485 bus RemoteClientId byte + // Enable writing to Vallox regisers, default false + EnableWrite bool + // Logge for debug, default no logging + LogDebug *log.Logger } type Vallox struct { @@ -24,10 +34,13 @@ type Vallox struct { in chan Event out chan valloxPackage lastActivity time.Time + writeAllowed bool + logDebug *log.Logger } const ( DeviceMulticast = 0x10 + DeviceMain = 0x11 RemoteClientMulticast = 0x20 ) @@ -61,7 +74,15 @@ type valloxPackage struct { Checksum byte } +var writeAllowed = map[byte]bool{FanSpeed: true} + +// Open opens the rs485 device specified in Config func Open(cfg Config) (*Vallox, error) { + + if cfg.LogDebug == nil { + cfg.LogDebug = log.New(ioutil.Discard, "", 0) + } + if cfg.RemoteClientId == 0 { cfg.RemoteClientId = 0x27 } @@ -84,6 +105,8 @@ func Open(cfg Config) (*Vallox, error) { remoteClientId: cfg.RemoteClientId, in: make(chan Event, 15), out: make(chan valloxPackage, 15), + writeAllowed: cfg.EnableWrite, + logDebug: cfg.LogDebug, } sendInit(vallox) @@ -94,30 +117,56 @@ func Open(cfg Config) (*Vallox, error) { return vallox, nil } +// Events returns channel for events from Vallox bus func (vallox Vallox) Events() chan Event { return vallox.in } +// ForMe returns true if event is addressed for this client func (vallox Vallox) ForMe(e Event) bool { return e.Destination == RemoteClientMulticast || e.Destination == vallox.remoteClientId } +// Query queries Vallox for register func (vallox Vallox) Query(register byte) { pkg := createQuery(vallox, register) vallox.out <- *pkg } +// SetSpeed changes speed of ventilation fan +func (vallox Vallox) SetSpeed(speed byte) { + if speed < 1 || speed > 8 { + vallox.logDebug.Printf("received invalid speed %x", speed) + return + } + value := speedToValue(int8(speed)) + vallox.logDebug.Printf("received set speed %x", speed) + // Send value to the main vallox device + vallox.writeRegister(DeviceMain, FanSpeed, value) + // Also publish value to all the remotes + vallox.writeRegister(RemoteClientMulticast, FanSpeed, value) +} + func sendInit(vallox *Vallox) { vallox.Query(FanSpeed) } +func (vallox Vallox) writeRegister(destination byte, register byte, value byte) { + pkg := createWrite(vallox, destination, register, value) + vallox.out <- *pkg +} + func createQuery(vallox Vallox, register byte) *valloxPackage { + return createWrite(vallox, DeviceMain, 0, register) +} + +func createWrite(vallox Vallox, destination byte, register byte, value byte) *valloxPackage { pkg := new(valloxPackage) pkg.System = 1 pkg.Source = vallox.remoteClientId - pkg.Destination = 0x11 - pkg.Register = 0 - pkg.Value = register + pkg.Destination = destination + pkg.Register = register + pkg.Value = value pkg.Checksum = calculateChecksum(pkg) return pkg } @@ -125,17 +174,39 @@ func createQuery(vallox Vallox, register byte) *valloxPackage { func handleOutgoing(vallox *Vallox) { for vallox.running { pkg := <-vallox.out + + if !isOutgoingAllowed(vallox, pkg.Register) { + vallox.logDebug.Printf("outgoing not allowed for %x = %x", pkg.Register, pkg.Value) + continue + } + now := time.Now() - if vallox.lastActivity.IsZero() || now.UnixMilli()-vallox.lastActivity.UnixMilli() < 100 { - time.Sleep(time.Millisecond * 100) + if vallox.lastActivity.IsZero() || now.UnixMilli()-vallox.lastActivity.UnixMilli() < 50 { + vallox.logDebug.Printf("delay outgoing to %x %x = %x, lastActivity %v now %v, diff %d ms", + pkg.Destination, pkg.Register, pkg.Value, vallox.lastActivity, now, now.UnixMilli()-vallox.lastActivity.UnixMilli()) + time.Sleep(time.Millisecond * 50) vallox.out <- pkg } else { updateLastActivity(vallox) binary.Write(vallox.port, binary.BigEndian, pkg) + vallox.logDebug.Printf("sent outgoing to %x %x = %x", pkg.Destination, pkg.Register, pkg.Value) } } } +func isOutgoingAllowed(vallox *Vallox, register byte) bool { + if register == 0 { + // queries are allowed + return true + } + + if !vallox.writeAllowed { + return false + } + + return writeAllowed[register] +} + func handleIncoming(vallox *Vallox) { vallox.running = true buf := make([]byte, 6) diff --git a/vallox_test.go b/vallox_test.go index 423f3f5..ddd0ee0 100644 --- a/vallox_test.go +++ b/vallox_test.go @@ -4,6 +4,17 @@ import ( "testing" ) +func TestOutGoingAllowed(t *testing.T) { + v := new(Vallox) + assertBoolean(true, isOutgoingAllowed(v, 0), t) + assertBoolean(false, isOutgoingAllowed(v, FanSpeed), t) + assertBoolean(false, isOutgoingAllowed(v, TempIncomingInside), t) + v.writeAllowed = true + assertBoolean(true, isOutgoingAllowed(v, 0), t) + assertBoolean(true, isOutgoingAllowed(v, FanSpeed), t) + assertBoolean(false, isOutgoingAllowed(v, TempIncomingInside), t) +} + func TestValueToTemp(t *testing.T) { assertTemp(0, -74, t) assertTemp(255, 100, t) @@ -22,6 +33,12 @@ func TestValueToSpeed(t *testing.T) { assertSpeed(255, 8, t) } +func assertBoolean(expected bool, value bool, t *testing.T) { + if expected != value { + t.Errorf("exptected %v got %v", expected, value) + } +} + func assertTemp(raw byte, value int8, t *testing.T) { if c := valueToTemp(raw); c != value { t.Errorf("temp %d was not converted to %d but to %d", raw, value, c)