comfoair
Reads and controls ventilation devices like Zehnder ComfoAir 350 with Node.js
Comfoair is an Open Source Node.js implementaion of the communication protocoll done and used by Zehnder. You can use this libary to control your personal ventialtion system.
This work based on the protocol description at http://www.see-solutions.de/sonstiges/Protokollbeschreibung_ComfoAir.pdf, the very usefull openHAB binding 'ComfoAir', the fantastic FHEM module 'ComfoAir' and a little bit research of my own. Hope you will like it.
Supports the following systems (afaik)
- Zehnder ComfoAir 350
- StorkAir WHR930
- Wernig/Santos G90-380
- Paul 370 DC
Status
Category | Status |
---|---|
Version | |
Dependencies | |
License |
Install
Simply
npm install comfoair
How to use
Connect your unit via serial bus and configure the port in your code. The comfoair library using serialport, so you can use every port (and syntax), that serialport is supporting. The speed (baud) should always be 9600, this is also default. If your ventilation device - like mine - is too far away from your controling system and you have lan/wlan access, you can pipe your serial port through ethernet with ser2net and socat. Look at this nice description for example: https://www.acmesystems.it/socat. The comfoair lib can either be uses as stream or simply by calling the given functions.
Examples
Depends on how you like it
By function call
Create an object and call one of the functions, given from the API.
To get your firmware version and device name:
const Comfoair = require('comfoair');
const ventilation = new Comfoair({
port: '/dev/ttyUSB0',
baud: 9600
});
ventilation.on('error', (err) => {
console.log('ERROR: ' + err.message);
});
ventilation.on('close', () => {
console.log('Connection to Comfoair closed');
});
ventilation.on('open', () => {
console.log('Connected to Comfoair :)');
ventilation.getTemperatures((err, resp) => {
if (err) console.log(err.message);
else console.log(resp);
ventilation.close();
});
});
To set the ventilation level:
const Comfoair = require('comfoair');
const ventilation = new Comfoair({
port: '/dev/ttyUSB0',
baud: 9600
});
ventilation.on('error', (err) => {
console.log('ERROR: ' + err.message);
});
ventilation.on('close', () => {
console.log('Connection to Comfoair closed');
});
ventilation.on('open', () => {
console.log('Connected to Comfoair :)');
ventilation.setLevel('middle', (err, resp) => {
if (err) console.log(err.message);
else console.log(resp);
ventilation.close();
});
});
As streaming object
Also you can use it as a duplex stream.
const ComfoairStream = require('comfoair').ComfoairStream;
const ventilationStream = new ComfoairStream({
port: '/dev/ttyUSB0',
baud: 9600
});
ventilationStream.on('error', (err) => {
console.log('ERROR: ' + err.message);
});
ventilationStream.on('close', () => {
console.log('Connection to Comfoair closed');
});
ventilationStream.on('data', chunk => {
console.log(chunk);
if (chunk.type === 'RES') {
ventilationStream.close();
}
});
const command = {
name: 'getFanState',
params: {}
};
ventilationStream.on('open', () => {
console.log('Connected to Comfoair :)');
ventilationStream.write(command, (err) => {
if (err) return console.log('ERROR: ' + err.message);
});
});
API
getBootloaderVersion
Request bootloader version and device type. No parameter required.
getBootloaderVersion(callback);
Response
{
"type": "RES",
"valid": true,
"payload": {
"description": "Bootloader version",
"major": {
"value": 3,
"label": "Version Major"
},
"minor": {
"value": 60,
"label": "Version Minor"
},
"beta": {
"value": 32,
"label": "Beta"
},
"deviceName": {
"value": "CA350 luxe",
"label": "Device name"
}
}
}
getFirmwareVersion
Request firmware version and device type. No parameter required.
getFirmwareVersion(callback);
Response
{
"type": "RES",
"valid": true,
"payload": {
"description": "Firmware version",
"major": {
"value": 3,
"label": "Version Major"
},
"minor": {
"value": 60,
"label": "Version Minor"
},
"beta": {
"value": 32,
"label": "Beta"
},
"deviceName": {
"value": "CA350 luxe",
"label": "Device name"
}
}
}
getFanState
Request state of the supply and the exhaust fan. No parameter required.
getFanState(callback);
Response
{
"type": "RES",
"valid": true,
"payload": {
"description": "Fan state",
"supplyAir": {
"value": 35,
"label": "Supply air",
"unit": "%"
},
"outgoingAir": {
"value": 35,
"label": "Outgoing air",
"unit": "%"
},
"rotationsSupply": {
"value": 1138,
"label": "Rotations supply",
"unit": "rpm"
},
"rotationsOutgoing": {
"value": 1120,
"label": "Rotations outgoing",
"unit": "rpm"
}
}
}
getFlapState
Request state of the bypass and preheater. No parameter required.
getFlapState(callback);
Response
{
"type": "RES",
"valid": true,
"payload": {
"description": "Flap state",
"bypass": {
"value": 0,
"label": "Bypass",
"unit": "%"
},
"preheat": {
"value": "Unknown",
"label": "Preheat"
},
"bypassMotorCurrent": {
"value": 0,
"label": "Bypass Motor Current",
"unit": "A"
},
"preheatMotorCurrent": {
"value": 0,
"label": "Preheat Motor Current",
"unit": "A"
}
}
}
getOperatingHours
Request operating hours of the different modes. No parameter required.
getOperatingHours(callback);
Response
{
"type": "RES",
"valid": true,
"payload": {
"description": "Operating hours",
"away": {
"value": 13492,
"label": "away",
"unit": "h"
},
"low": {
"value": 12833,
"label": "low",
"unit": "h"
},
"middle": {
"value": 7699,
"label": "middle",
"unit": "h"
},
"frostProtection": {
"value": 662,
"label": "frost protection",
"unit": "h"
},
"preHeating": {
"value": 0,
"label": "preheating",
"unit": "h"
},
"bypassOpen": {
"value": 10008,
"label": "bypass open",
"unit": "h"
},
"filter": {
"value": 1825,
"label": "filter",
"unit": "h"
},
"high": {
"value": 1068,
"label": "high",
"unit": "h"
}
}
}
getVentilationLevel
Request ventilation levels. No parameter required.
getVentilationLevel(callback);
Response
{
"type": "RES",
"valid": true,
"payload": {
"description": "Get ventilation levels",
"exhaustAway": {
"value": 15,
"label": "Exhaust fan level away",
"unit": "%"
},
"exhaustLow": {
"value": 35,
"label": "Exhaust fan level low",
"unit": "%"
},
"exhaustMiddle": {
"value": 50,
"label": "Exhaust fan level middle",
"unit": "%"
},
"supplyAway": {
"value": 15,
"label": "Supply fan level away",
"unit": "%"
},
"supplyLow": {
"value": 35,
"label": "Supply fan level low",
"unit": "%"
},
"supplyMiddle": {
"value": 50,
"label": "Supply fan level middle",
"unit": "%"
},
"exhaustCurrent": {
"value": 15,
"label": "Current exhaust fan level",
"unit": "%"
},
"supplyCurrent": {
"value": 15,
"label": "Current supply fan level",
"unit": "%"
},
"currentLevel": {
"value": 1,
"label": "Current ventilation level",
},
"supplyFanRunning": {
"value": true,
"label": "Supply fan is running"
},
"exhaustHigh": {
"value": 70,
"label": "Exhaust fan level high",
"unit": "%"
},
"supplyHigh": {
"value": 70,
"label": "Exhaust fan level high",
"unit": "%"
}
}
}
getTemperatures
Request current temperatures. No parameter required.
getTemperatures(callback);
Response
{
"type": "RES",
"valid": true,
"payload": {
"description": "Temperatures",
"comfort": {
"value": 21,
"label": "comfort",
"unit": "°C"
},
"outsideAir": {
"value": 11,
"label": "outside air",
"unit": "°C"
},
"supplyAir": {
"value": 20.5,
"label": "supply air",
"unit": "°C"
},
"outgoingAir": {
"value": 19.5,
"label": "outgoing air",
"unit": "°C"
},
"exhaustAir": {
"value": 11.5,
"label": "exhaust air",
"unit": "°C"
},
"sensorConnected": {
"value": [],
"label": "sensor connected"
},
"groundHeatExchanger": {
"value": 0,
"label": "ground heat exchanger",
"unit": "°C"
},
"preheating": {
"value": 0,
"label": "preheating",
"unit": "°C"
},
"cookerHood": {
"value": 0,
"label": "cooker hood",
"unit": "°C"
}
}
}
getTemperatureStates
Request temperature states. No parameter required.
getTemperatureStates(callback);
Response
{
"type": "RES",
"valid": true,
"payload": {
"description": "Temperature states",
"outsideAir": {
"value": 11,
"label": "outside air",
"unit": "°C"
},
"supplyAir": {
"value": 20.5,
"label": "supply air",
"unit": "°C"
},
"outgoingAir": {
"value": 19,
"label": "outgoing air",
"unit": "°C"
},
"exhaustAir": {
"value": 11.5,
"label": "exhaust air",
"unit": "°C"
}
}
}
getFaults
Request operation faults. No parameter required.
getFaults(callback);
Response
{
"description":"Operating faults",
"currentErrorA":{
"value":"A0",
"label":"current error A"
},
"currentErrorE":{
"value":0,
"label":"current error E"
},
"lastErrorA":{
"value":"A0",
"label":"last error A"
},
"lastErrorE":{
"value":0,
"label":"last error E"
},
"penultimateErrorA":{
"value":"A0",
"label":"penultimate error A"
},
"penultimateErrorE":{
"value":0,
"label":"penultimate error E"
},
"antepenultimateErrorA":{
"value":"A0",
"label":"antepenultimate error A"
},
"antepenultimateErrorE":{
"value":0,
"label":"antepenultimate error E"
},
"replaceFilter":{
"value":true,
"label":"replace filter"
},
"currentErrorEA":{
"value":0,
"label":"current error EA"
},
"lastErrorEA":{
"value":0,
"label":"last error EA"
},
"penultimateErrorEA":{
"value":0,
"label":"penultimate error EA"
},
"antepenultimateErrorEA":{
"value":0,
"label":"antepenultimate error EA"
},
"currentErrorAHigh":{
"value":0,
"label":"current error A high"
},
"lastErrorAHigh":{
"value":0,
"label":"last error A high"
},
"penultimateErrorAHigh":{
"value":0,
"label":"penultimate error A high"
},
"antepenultimateErrorAHigh":{
"value":0,
"label":"antepenultimate error A high"
},
"type":"RES"
}
setLevel
Set the ventilation level.
Parameter | Type | Description |
---|---|---|
level | string/number | 'away', 'low', 'middle', 'high', 0, 1, 2, 3, 'auto' |
Where 'away' is the same as 0, 'low' is the same as 1 and so on. The numbers 0 to 3 can also be passed als string. The setting 'auto' seems to be for the external Panel for the ventilation system and isn't testet.
setLevel('high', callback);
Response
{
"type": "ACK"
}
setComfortTemperature
Set the comfort Temperature.
Parameter | Type | Description |
---|---|---|
temperature | number | Temperature to be held by the device |
setComfortTemperature(20, callback);
Response
{
"type": "ACK"
}
setVentilationLevel
Set the rotaition speed of the different ventilation levels for supply and exhaust fan.
Parameter | Type | Description |
---|---|---|
exhaustAway | number | Speed for away level, exhaust fan |
exhaustLow | number | Speed for low level, exhaust fan |
exhaustMiddle | number | Speed for middle level, exhaust fan |
exhaustHigh | number | Speed for high level, exhaust fan |
supplyAway | number | Speed for away level, supply fan |
supplyLow | number | Speed for low level, supply fan |
supplyMiddle | number | Speed for middle level, supply fan |
supplyHigh | number | Speed for high level, supply fan |
setVentilationLevel(15, 35, 50, 70, 15, 35, 50, 70, callback);
Response
{
"type": "ACK"
}
reset
Reset faults/settings/filter timer or run self test.
Parameter | Type | Description |
---|---|---|
resetFaults | boolean | Clear all faults |
resetSettings | boolean | Reset settings |
runSelfTest | boolean | Run self test |
resetFilterTime | boolean | Rest filter timer |
reset(false, false, false, true, callback);
Response
{
"type": "ACK"
}
runCommand
It is also possible to pass the commands as strings. For this you can use this function.
const commandName = 'setLevel';
const params = { level: 'away' };
runCommand(commandName, params, (err, response) => {
if (err) return console.log(err.message);
console.log(response);
});
VincentSC
Additional info fromZehnder talks about intake, exhaust, supply and extract. Or extract, supply, incoming and outgoing.
This library uses supply, exhaust, outgoing and outside.
This is my guess how it translates:
outside
= intake / incomingsupply
= supplyexhaust
= extractoutgoing
= exhaust / outgoing
Migration to 1.x.x
There are only tiny API changes. The main difference is in using this module in streaming Mode. In Version 0.x.x you can simply use the same Module for function calls and streaming. Starting with 1.0.0 you have to choose the streaming module explicit.
0.x.x
const Comfoair = require('comfoair');
const ventilationStream = new Comfoair({
port: '/dev/ttyUSB0',
baud: 9600
});
1.x.x
const ComfoairStream = require('comfoair').ComfoairStream;
const ventilationStream = new ComfoairStream({
port: '/dev/ttyUSB0',
baud: 9600
});