Re: OTmonitor and Domoticz parallel access to OTGW
Posted: Monday 26 December 2016 18:51
@jake let's see if we can get this done this XMas
I have changed it a little, you can now tell which data field is to be replaced by 'fieldnumberInPsOutputToReplace'. As there are 25 of them in the PS1 output, choose wisely (and yes, I've filled in the default fieldnumber 16 now (+1 ) (and in the code is the complete list as on the OTGW website . Next to that you can also choose the Opentherm ID of what to put in that (e.g. 29 (Decimal, not hex) for the Solar).
Specifying id = id is a bit more complicated, as I then have to get all the IDs used in the first place, although they are now in copied list of course.
BTW Hope the Dutch is gone now
I have changed it a little, you can now tell which data field is to be replaced by 'fieldnumberInPsOutputToReplace'. As there are 25 of them in the PS1 output, choose wisely (and yes, I've filled in the default fieldnumber 16 now (+1 ) (and in the code is the complete list as on the OTGW website . Next to that you can also choose the Opentherm ID of what to put in that (e.g. 29 (Decimal, not hex) for the Solar).
Code: Select all
#!/usr/bin/node
/*
Make a small listener that Domoticz can connect to as if it was the real otmonitor application.
Then relay the messages between OTGW and Domoticz such that otmonitor is still fully functional.
author: ernorv
v1.0 - Special
v1.0 - Special with '=>' replaced for better compatibility
*/
const portNumberForDomoticzToUse = 7689; // The new port that Domoticz needs to use. In Domoticz, set this number as the port under the OTGW hardware
const portNumberOfOtMonitor = 7686; // The network port OTGW provides, as can be set under the Configuration parts
const replaceTTwithTC = true; // If true: use TC instead of TT (if false, just standard Domoticz TT usage)
// Normally no further settings required below this line
// -------------- some very specific settings, do not normally use
const resetOTGW_PS_state = true; // default true: sets the PS state back to 0 after Domoticz has its data, is what allows otmonitor to keep making nice graphs as if Domoticz was not there
// just if you really know what you do, set these
const replaceAnIdInDomoticzOutput = false; // default: false! set this to true for replacement of the DHW Setpoing with Solar Temperature
const idToUseForReplacement = 29; // (Decimal) ID of the Opentherm message to use. 29 refers to the Solar, I use 25 for test purposes
const fieldnumberInPsOutputToReplace = 16; // which field to replace in PS output to replace:
/*
The PS1 output has 25 field: see http://otgw.tclcode.com/firmware.html#dataids
The OTGW outputs the following fields (normally that is). I have given them below. Set the 'fieldnumberInPsOutputToReplace' to the
fieldnumber in accordance with what you like to be replace, given these fields (default is 16):
1) Status (MsgID=0) - Printed as two 8-bit bitfields
2) Control setpoint (MsgID=1) - Printed as a floating point value
3) Remote parameter flags (MsgID= 6) - Printed as two 8-bit bitfields
4) Maximum relative modulation level (MsgID=14) - Printed as a floating point value
5) Boiler capacity and modulation limits (MsgID=15) - Printed as two bytes
6) Room Setpoint (MsgID=16) - Printed as a floating point value
7) Relative modulation level (MsgID=17) - Printed as a floating point value
8) CH water pressure (MsgID=18) - Printed as a floating point value
9) Room temperature (MsgID=24) - Printed as a floating point value
10) Boiler water temperature (MsgID=25) - Printed as a floating point value
11) DHW temperature (MsgID=26) - Printed as a floating point value
12) Outside temperature (MsgID=27) - Printed as a floating point value
13) Return water temperature (MsgID=28) - Printed as a floating point value
14) DHW setpoint boundaries (MsgID=48) - Printed as two bytes
15) Max CH setpoint boundaries (MsgID=49) - Printed as two bytes
16) DHW setpoint (MsgID=56) - Printed as a floating point value
17) Max CH water setpoint (MsgID=57) - Printed as a floating point value
18) Burner starts (MsgID=116) - Printed as a decimal value
19) CH pump starts (MsgID=117) - Printed as a decimal value
20) DHW pump/valve starts (MsgID=118) - Printed as a decimal value
21) DHW burner starts (MsgID=119) - Printed as a decimal value
22) Burner operation hours (MsgID=120) - Printed as a decimal value
23) CH pump operation hours (MsgID=121) - Printed as a decimal value
24) DHW pump/valve operation hours (MsgID=122) - Printed as a decimal value
25) DHW burner operation hours (MsgID=123) - Printed as a decimal value
*/
// ----------------------------------------------------------
var net = require('net');
var server
var otgwSocket
var domSocketsArray = [];
// -------------- DEFINE SERVER LISTENING FOR CONNECTIONS FROM DOM ---------------
server = net.createServer(function (socket) {
console.log('Incoming Domoticz Connection...');
domSocketsArray.push(socket);
socket.on('data', function (data) {
data = data.toString(); // data may come in as Buffer
console.log('DOM: ' + data.replace(/[\r\n]*$/, "").replace(/\r\n/g, ','));
if (! otgwSocket.destroyed) {
if (replaceTTwithTC) {
otgwSocket.write(data.replace("TT=", "TC="));
} else {
otgwSocket.write(data);
};
}
});
socket.on('end', function() {
console.log('end');
domSocketsArray.splice(domSocketsArray.indexOf(socket), 1);
});
socket.on('error', function () {
console.log('DOM: detected error on socket: probably otgwSocket died');
domSocketsArray.splice(domSocketsArray.indexOf(socket), 1);
});
socket.on('close', function () {
console.log('DOM: socket closed');
domSocketsArray.splice(domSocketsArray.indexOf(socket), 1);
});
});
server.on('error', function(err) {
throw err;
});
// ---------------- CONNECT TO OTGW ----------------
function relayToClients(data) {
for (var i =0 ; i < domSocketsArray.length; i++) {
if (! domSocketsArray[i].destroyed) {
domSocketsArray[i].write(data);
}
}
}
var resetPS1 = false;
var currentSolarTemperature = 0
function connectToOTGW() {
otgwSocket = net.createConnection({port: portNumberOfOtMonitor}, function() {
console.log('Connected to OTGW server at port number ' + portNumberOfOtMonitor + '!');
});
otgwSocket.on('data', function(data) {
data = data.toString(); // data comes in as Buffer, we want to use it as string
if (data.match(/[BTAR][0-9ABCDEF,]{8}/)) {
// this data is not for Domoticz, it is what makes the normal otmonitor app work
// console.log('<not relayed> OTGW: ' + data.toString().replace("\r", '').replace('\n', ''));
// for solar temperature we are looking for the following: B C01D 0F00
// solar temp: id 29 R - Solar storage temperature f8.8 -40..127 Solar storage temperature (°C)
// for test purposes, lets look at B40192B00 : 49.00 degrees of boiler water temperature
// 25 R - Boiler water temp. f8.8 -40..127 Flow water temperature from boiler (°C)
// note: sign bit!
// byte 2 is data-id, byte 3 and 4 contain the data value
// byte 1: parity bit, 3 bits msg type and 4 bits spare, hence mind the parity
if (replaceAnIdInDomoticzOutput && (data[0] == 'B')) {
if ((parseInt(data.substr(1,2), 16)&0x70) == 0x40){ // test whether this is a read-ack
//console.log('that was a read-ack')
if ((parseInt(data.substr(3,2), 16)) == idToUseForReplacement){ // new data to use
var f1, f2, s
s = parseInt(data.substr(5,2))&0x80;
f1 = parseInt(data.substr(5,2), 16)&0x7F;
f2 = parseInt(data.substr(7,2), 16);
currentSolarTemperature = f1 + f2/256.0;
if (s > 0) currentSolarTemperature *= -1;
console.log(data.replace(/[\n\l\r]/g, "") + ': New replacement Temperature with temp ' + currentSolarTemperature.toFixed(2))
}
}
}
} else {
console.log('OTGW: ' + data.replace(/[\r\n]*$/, "").replace(/\r\n/g, ','));
// sometimes there is a PR:,I=00 on the line which ends up in Domoticz status log. It looks like
// malfomed data coming from the OTGW itself. Let's do some cleaning up, keeping the status log clean
data = data.replace(/PR:[ ]*,/g, "PR: ");
data = data.replace(/PR, /g, "PR: ");
// a difficult one: this happens every so often, a comma missing in the output of the OTGW PS=0 output, let's repair this
data = data.replace(/([01]{8}\/[01]{8},[0-9\.]{4,6},[01]{8}\/[01]{8})([0-9\.]{4,6}.*)/, "$1,$2");
/* Now we can replace a field in the output to Domoticz with another one, as long as the one to read has also a
* floating point definition (bit technical, but if you like, google for the specification of the Opentherm itself).
* This implies that we can only put it into a Domoticz field that also understand this floating point definition.
* Most temperatures are by the way. Also the (Opentherm ID) needs to be filled in. These are both done above in the
* const definition section of the script.
*/
if (replaceAnIdInDomoticzOutput) {
tmp = data.split(',')
if (tmp.length == 25) { // for the PS1 output we expect 25 fields: see http://otgw.tclcode.com/firmware.html#dataids
tmp[fieldnumberInPsOutputToReplace] = currentSolarTemperature.toFixed(2);
data = tmp.join(",");
console.log("RPLC: " + data);
}
}
relayToClients(data);
}
if (data.indexOf('PS: 1') !== -1 ) {
// Domoticz is issueing a PS1 command, reset it if indicated by the settings above
resetPS1 = resetOTGW_PS_state;
};
if (resetPS1 && (data.match(/[01]{7,9}\/[01]{7,9},.*/))) {
// ok the data requested by domoticz has been produced, now go back to normal otmonitor operation
otgwSocket.write('PS=0\r\n'); // no need to test for destroyed here, data just arrived from it
resetPS1 = false;
};
});
otgwSocket.on('end', function() {
console.log('OTGW disconnected from server, reconnecting in 1 second.');
setTimeout(function() { connectToOTGW(); }, 1000)
});
}
connectToOTGW();
// ---------------------- start the server for active listening ----------------
server.listen(portNumberForDomoticzToUse, function() {
console.log('Server for Domoticz is now listening, point Domoticz OTGW Hardware to port '+ portNumberForDomoticzToUse + '.');
});
console.log('Application started.');
BTW Hope the Dutch is gone now