Skip to content

Commit

Permalink
Support changing ventilation speed
Browse files Browse the repository at this point in the history
  • Loading branch information
pvainio committed Oct 29, 2021
1 parent 2086216 commit 3c09b2f
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 10 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/pvainio/vallox
module github.com/pvainio/vallox-rs485

go 1.17

Expand Down
83 changes: 77 additions & 6 deletions vallox.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package valloxrs485 implements Vallox RS485 protocol
package valloxrs485

import (
Expand All @@ -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 {
Expand All @@ -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
)

Expand Down Expand Up @@ -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
}
Expand All @@ -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)
Expand All @@ -94,48 +117,96 @@ 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
}

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)
Expand Down
17 changes: 17 additions & 0 deletions vallox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 3c09b2f

Please sign in to comment.