iobroker.e3oncan
Version:
Collect data on CAN bus for Viessmann E3 devices, e.g. Vitocal, Vitocharge, Energy Meters E380CA and E3100CB
211 lines (196 loc) • 9.75 kB
JavaScript
const storage = require('./storage');
class collect {
constructor(config) {
this.config = config;
this.config.statId = 'statCollect';
this.config.worker = 'collect';
this.storage = new storage.storage(this.config);
this.ts = {};
this.msDelay = config.delay*1000;
this.timeoutHandle = null;
this.maxDid = 3500;
this.commBusy = false; // Communication routine running
this.data = {
'len' : 0,
'timestamp' : 0,
'databytes' : [],
'did' : 0,
'collecting': false,
'D0expected': 0x21
};
this.stat = {
state : 'standby',
cntCommTotal : 0, // Number collected dids
cntCommOk : 0, // Number of ok
cntCommStored : 0, // Number of dids stored
cntCommTimeout : 0, // Number of timeouts
cntCommBadProt : 0, // Number of bad communications
cntTooBusy : 0, // Number of conflicting calls of msgCollect()
nextTs : 0, // Timestamp for next storage (earliest)
tsMinStep : 5000 // Minimum time step between storages
};
}
async initStates(ctx, opMode) {
await this.storage.initStates(ctx, opMode);
this.stat.state = 'standby';
await this.storage.storeStatistics(ctx, this, true);
}
async onTimeout(ctxGlobal, ctxLocal) {
ctxGlobal.log.error('Collect timeout on 0x'+Number(ctxLocal.config.canID[0]).toString(16)+'.'+String(ctxLocal.data.did));
ctxLocal.stat.cntCommTimeout += 1;
ctxLocal.data.collecting = false;
}
async startup(ctx) {
this.stat.state = 'active';
await this.storage.storeStatistics(ctx, this, true);
this.data.collecting = false;
await this.storage.setOpMode('normal');
await ctx.log.info('Collect worker started on '+this.config.stateBase);
ctx.cntWorkersActive += 1;
}
async stop(ctx) {
try {
await this.storage.setOpMode('standby');
// Stop Timeout:
if (this.timeoutHandle) await ctx.clearTimeout(this.timeoutHandle);
this.timeoutHandle = null;
this.stat.state = 'stopped';
await this.storage.storeStatistics(ctx, this, true);
// Stop worker:
this.data.collecting = false;
ctx.log.info('Collect worker stopped on '+this.config.stateBase);
} catch (e) {
ctx.log.error('Collect worker on '+this.config.stateBase+' could not be stopped. err='+e.message);
}
ctx.cntWorkersActive -= 1;
}
async msgCollect(ctx, msg) {
if (await this.storage.getOpMode() == 'standby') return; // No communication in mode 'standby'
if (this.commBusy) {
this.stat.cntTooBusy += 1;
if (this.stat.cntTooBusy == 1) ctx.log.warn('Collect worker error on '+this.config.stateBase+': Evaluation of messages overloaded.');
return;
}
this.commBusy = true;
const candata = msg.data.toJSON().data;
const canid = msg.id;
const msgDlc = candata.length;
const tsNow = new Date().getTime();
const D3 = candata[3];
switch (this.config.device) {
case 'e380':
// Energy meter E380
this.stat.cntCommTotal += 1;
this.stat.cntCommOk += 1;
if (!(canid in this.ts)) { this.ts[canid] = tsNow; }
if ( (this.config.delay == 0) || ((tsNow >= this.ts[canid])) ) {
this.stat.cntCommStored += 1;
this.storage.decodeDataCAN(ctx,this,msg.id,candata);
this.ts[canid] = tsNow+this.msDelay;
}
break;
case 'e3100cb':
// Energy meter E3100CB
this.stat.cntCommTotal += 1;
this.stat.cntCommOk += 1;
if (!(canid in this.ts)) { this.ts[canid] = tsNow; }
if ( (this.config.delay == 0) || ((tsNow >= this.ts[canid])) ) {
this.stat.cntCommStored += 1;
const didStr = '00'+String(D3);
this.storage.decodeDataCAN(ctx,this,String(msg.id)+'_'+didStr.slice(-2),candata.slice(4));
this.ts[canid] = tsNow+this.msDelay;
}
break;
default:
// E3 device
if (this.data.collecting) {
if (candata[0] == this.data.D0expected) {
// append next part of data
this.data.databytes = this.data.databytes.concat(candata.slice(1));
if (this.data.databytes.length >= this.data.len) {
// All data received
if (this.timeoutHandle) await ctx.clearTimeout(this.timeoutHandle);
this.stat.cntCommOk += 1;
if (!(this.data.did in this.ts)) { this.ts[this.data.did] = tsNow; }
if ( (this.config.delay == 0) || ((tsNow >= this.ts[this.data.did])) ) {
this.stat.cntCommStored += 1;
this.storage.decodeDataCAN(ctx, this, this.data.did, this.data.databytes.slice(0,this.data.len));
this.ts[this.data.did] = tsNow+this.msDelay;
}
this.data.collecting = false;
} else {
// More data to come
this.data.D0expected += 1;
if (this.data.D0expected > 0x2f) {
this.data.D0expected = 0x20;
}
}
}
}
if ( (!this.data.collecting) && (msgDlc > 4) && (candata[0] == 0x21) && (D3 >= 0xb0) && (D3 < 0xc0)) {
this.data.D0expected = candata[0];
this.data.did = candata[1]+256*candata[2];
this.data.timestamp = msg.ts_sec*1000+Math.round(msg.ts_usec/1000);
if ( (this.data.did > 0) && (this.data.did < this.maxDid) ) {
switch (D3) {
case 0xb1:
case 0xb2:
case 0xb3:
case 0xb4:
// Single Frame B1,B2,B3,B4
this.data.len = D3-0xb0;
this.data.databytes = candata.slice(4);
this.stat.cntCommTotal += 1;
this.stat.cntCommOk += 1;
if (!(this.data.did in this.ts)) { this.ts[this.data.did] = tsNow; }
if ( (this.config.delay == 0) || ((tsNow >= this.ts[this.data.did])) ) {
this.stat.cntCommStored += 1;
this.storage.decodeDataCAN(ctx, this, this.data.did, this.data.databytes.slice(0,this.data.len));
this.ts[this.data.did] = tsNow+this.msDelay;
}
break;
case 0xb0:
// Multi Frame B0
this.data.D0expected = candata[0]+1;
this.stat.cntCommTotal += 1;
if (candata[4]==0xc1) {
this.data.databytes = candata.slice(6);
this.data.len = candata[5];
} else {
this.data.databytes = candata.slice(5);
this.data.len = candata[4];
}
this.timeoutHandle = ctx.setTimeout(this.onTimeout, this.config.timeout, ctx, this);
this.data.collecting = true;
break;
case 0xb5:
case 0xb6:
case 0xb7:
case 0xb8:
case 0xb9:
case 0xba:
case 0xbb:
case 0xbc:
case 0xbd:
case 0xbe:
case 0xbf:
// Multi Frame B6 .. BF
this.stat.cntCommTotal += 1;
this.data.D0expected = candata[0]+1;
this.data.databytes = candata.slice(4);
this.data.len = D3-0xb0;
this.timeoutHandle = ctx.setTimeout(this.onTimeout, this.config.timeout, ctx, this);
this.data.collecting = true;
break;
default:
ctx.log.debug('Collect worker error on '+this.config.stateBase+': Unplausible byte D3: Did='+String(this.data.did)+' D3=0x'+Number(D3).toString(16)+' candata='+this.storage.storageDids.arr2Hex(candata));
}
}
}
}
this.commBusy = false;
}
}
module.exports = {
collect
};