UNPKG

iobroker.e3oncan

Version:

Collect data on CAN bus for Viessmann E3 devices, e.g. Vitocal, Vitocharge, Energy Meters E380CA and E3100CB

873 lines (813 loc) 44.3 kB
const storage = require('./storage'); class scheduleLoop { constructor(ctxGlobal, ctxLocal, schedule) { this.ctxGlobal = ctxGlobal; this.ctxLocal = ctxLocal; this.schedule = schedule; this.dids = []; this.schedHandle = null; } async startSchedule(ctx) { if (this.schedule == 0) { await this.ctxGlobal.log.silly('UDS schedule one time: '+this.ctxLocal.canIDhex+'.'+JSON.stringify(this.dids)); await this.ctxLocal.pushCmnd(this.ctxGlobal, 'read', this.dids); this.schedHandle = null; } else { this.schedHandle = ctx.setInterval(async () => { await this.loop(); }, this.schedule*1000); } } async stopSchedule(ctx) { try { if (this.schedHandle) await ctx.clearInterval(this.schedHandle); await this.ctxGlobal.log.silly('UDS schedule stopped: '+String(this.schedule)+' '+this.ctxLocal.canIDhex+'.'+JSON.stringify(this.dids)); } catch (e) { // Dod nothing } } async addDids(dids) { const didsArr = dids.replace(' ','').split(','); this.dids = this.dids.concat(didsArr.map(function(str) { return parseInt(str); })); } async loop() { await this.ctxGlobal.log.silly('UDS schedule: '+String(this.schedule)+' '+this.ctxLocal.canIDhex+'.'+JSON.stringify(this.dids)); await this.ctxLocal.pushCmnd(this.ctxGlobal, 'read', this.dids); } } class uds { constructor(config) { this.config = config; this.config.statId = 'statUDS'; this.config.worker = 'uds'; this.storage = new storage.storage(this.config); this.states = ['standby','waitForFFrbd','waitForCFrbd','waitForFFSFwbd','waitForFFMFwbd']; this.readByDidProt = { 'idTx' : this.config.canID, 'idRx' : Number(this.config.canID) + 0x10, 'PCI' : 0x03, // Protocol Control Information 'SIDtx' : 0x22, // Service ID transmit 'SIDrx' : 0x62, // Service ID receive 'SIDcf' : 0x03, // Service ID confirmation byte 'SIDnr' : 0x7F, // SID negative response 'FC' : [0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00], // Flow Control frame }; this.writeByDidProt = { 'idTx' : this.config.canID, 'idRx' : Number(this.config.canID) + 0x10, 'PCI' : 0x00, // Protocol Control Information = length of data +3 'SIDtx' : 0x2E, // Service ID transmit 'SIDrx' : 0x6E, // Service ID receive 'SIDcf' : 0x03, // Service ID confirmation byte 'SIDnr' : 0x7F, // SID negative response 'FCrx' : 0x30, // Flow Control ID for MF transfer }; this.writeByDidSID77Prot = { 'idTx' : this.config.canID, 'idRx' : Number(this.config.canID) + 0x10, 'PCI' : 0x00, // Protocol Control Information = length of data +3 'SIDtx' : 0x77, // Service ID transmit 'SIDrx' : 0x77, // Service ID receive 'SIDcf' : 0x04, // Service ID confirmation byte 'SIDnr' : 0x7F, // SID negative response 'FCrx' : 0x30, // Flow Control ID for MF transfer }; this.writeProt = this.writeByDidProt; // Default: Use standard protocol for writeDataByIdentifier this.data = { 'len' : 0, 'tsRequest' : 0, 'tsReply' : 0, 'tsTotal' : 0, 'valRaw' : Array(0), 'databytes' : Array(0), 'did' : 0, 'state' : 0, 'D0' : 0x21, 'txPos' : 0 }; this.canIDhex = '0x'+Number(this.config.canID).toString(16); this.SID77addrOffset = 0x02; this.cmndsQueue = []; this.cmndsHandle = null; this.cmndsUpdateTime = 40; // Check for new commands (ms) this.busy = false; // Worker is busy this.commBusy = false; // Communication routine running this.schedules = {}; this.userReadByDidId = this.config.stateBase+'.cmnd.udsReadByDid'; this.timeoutHandle = null; this.callback = null; this.coolDownTs = 0; // Earliest time for next communication this.stat = { state : 'standby', CANdevAddr : '', // CAN device address (hex) cntCommTotal : 0, // Number of startes communications cntCommOk : 0, // Number of succesfull communications cntCommNR : 0, // Number of communications ending in negative response cntCommTimeout : 0, // Number of communications ending in timeout cntCommBadProtocol : 0, // Number of bad communications, e.g. bad frame cntCommFailedPerDid : {}, // Number of communications failed (timeout or bad protocol) for specific did cntTooBusy : 0, // Number of conflicting calls of msgUds() replyTime : {min:this.config.timeout,max:0,mean:0}, nextTs : 0, // Timestamp for next storage (earliest) tsMinStep : 5000 // Minimum time step between storages }; } async initStates(ctx, opMode) { await this.storage.initStates(ctx, opMode); if (['standby','normal','udsDidScan'].includes(opMode)) { await ctx.setObjectNotExistsAsync(this.config.stateBase+'.cmnd', { type: 'channel', common: { name: this.config.stateBase+' commands', }, native: {}, }); await ctx.setObjectNotExistsAsync(this.userReadByDidId, { type: 'state', common: { name: 'List of dids to be read via UDS ReadByDid. Place command with ack=false.', type: 'json', role: 'state', read: true, write: true, }, native: {}, }); await ctx.setStateAsync(this.userReadByDidId, { val: JSON.stringify([]), ack: true }); await this.storage.storeStatistics(ctx, this, true); } this.stat.state = 'standby'; this.stat.CANdevAddr = this.canIDhex; } async startup(ctx, opMode) { await this.setComState(0); await this.setWorkerOpMode(opMode); this.stat.state = 'active'; await this.storage.storeStatistics(ctx, this, true); if (opMode == 'normal') { for (const sched of Object.values(this.schedules)) { // Start schedules on startup and do one-time schedules await sched.startSchedule(ctx); } } if (opMode == 'normal') { await ctx.log.info('UDS worker started on '+this.config.stateBase+' ('+this.canIDhex+') with '+String(Object.keys(this.schedules).length)+' active schedule(s).'); } else { await ctx.log.silly('UDS worker started in mode '+opMode+' on '+this.config.stateBase+' ('+this.canIDhex+') with '+String(Object.keys(this.schedules).length)+' active schedule(s).'); } if (opMode != 'service77') { this.cmndsHandle = ctx.setInterval(async () => { await this.cmndsLoop(ctx); }, this.cmndsUpdateTime); } ctx.cntWorkersActive += 1; } async stop(ctx) { try { if (this.stat.state == 'stopped') return; this.stat.state = 'stopped'; const opMode = await this.getWorkerOpMode(); await this.storage.storeStatistics(ctx, this, true); await this.storage.setOpMode('standby'); // Stop loops: for (const sched of Object.values(this.schedules)) { await sched.stopSchedule(ctx); } if (this.cmndsHandle) await ctx.clearInterval(this.cmndsHandle); // Stop Timeout: if (this.timeoutHandle) await ctx.clearTimeout(this.timeoutHandle); this.timeoutHandle = null; // Stop worker: this.callback = null; if (opMode == 'normal') { ctx.unsubscribeStates(ctx.namespace+'.'+this.config.stateBase+'.*'); ctx.log.info('UDS worker stopped on '+this.config.stateBase); } else { ctx.log.silly('UDS worker stopped in mode '+opMode+' on '+this.config.stateBase+' ('+this.canIDhex+')'); } } catch (e) { ctx.log.error('UDS worker on '+this.config.stateBase+' could not be stopped. err='+e.message); } ctx.cntWorkersActive -= 1; } async setWorkerOpMode(opMode) { await this.storage.setOpMode(opMode); } async getWorkerOpMode() { return this.storage.getOpMode(); } async getComState() { return this.data.state; } async setComState(comState) { this.data.state = comState; } async setDidDone(ctx, coolDownTime) { // Finalize communication for recent did this.coolDownTs = new Date().getTime()+coolDownTime; if (this.cmndsQueue.length == 0) this.busy = false; await this.setComState(0); if (this.timeoutHandle) await ctx.clearTimeout(this.timeoutHandle); } async setDidStart(ctx, did, mode, len) { switch (mode) { case 'read': await this.setComState(1); // 'waitForFFrbd' break; case 'write': if (len<=4) { // Single frame communication await this.setComState(3); // 'waitForFFSFwbd' } else { // Multi frame communication await this.setComState(4); // 'waitForFFMFwbd' } break; default: ctx.log.warn('UDS worker started on '+this.config.stateBase+': mode '+mode+' not implemented.'); } const tsNow = new Date().getTime(); const minWaiting = this.coolDownTs - tsNow; if (minWaiting > 0) await this.sleep(ctx, minWaiting); this.busy = true; this.timeoutHandle = await ctx.setTimeout(this.onTimeout, this.config.timeout, ctx, this); this.data.did = did; this.data.tsRequest = tsNow; } async calcStat() { this.data.tsReply = new Date().getTime(); const rt = this.data.tsReply - this.data.tsRequest; this.data.tsTotal += rt; if (rt < this.stat.replyTime.min) this.stat.replyTime.min = rt; if (rt > this.stat.replyTime.max) this.stat.replyTime.max = rt; this.stat.replyTime.mean = Math.round(this.data.tsTotal/this.stat.cntCommOk); } async setCallback(callback) { this.callback = callback; } async addSchedule(ctx, schedule, dids) { if (!Object.keys(this.schedules).includes(String(schedule))) { // New schedule this.schedules[schedule] = new scheduleLoop(ctx, this, schedule); await ctx.log.silly('UDS worker on '+this.config.stateBase+': Added schedule '+String(schedule)+'s.'); } await this.schedules[schedule].addDids(dids); await ctx.log.silly('UDS worker on '+this.config.stateBase+': Added dids to schedule '+String(schedule)+'s '+JSON.stringify(this.schedules[schedule].dids)); } async pushCmnd(ctx, mode, dids) { await ctx.log.silly('UDS worker on '+this.config.stateBase+': pushCmnd(): '+mode+' '+String(this.canIDhex)+'.'+String(JSON.stringify(dids))); if (Array.isArray(dids)) { for (const did of Object.values(dids)) { await this.cmndsQueue.push({'mode':mode, 'did': did}); } } else { await ctx.log.warn('UDS worker warning on '+this.config.stateBase+': Wrong format for command. dids have to be array. Got dids='+JSON.stringify(dids)); } } sleep(ctx, milliseconds) { return new Promise(resolve => ctx.setTimeout(resolve, milliseconds)); } async startupUdsWorkerService77(ctx, addr) { const udsWorker = new uds( { 'canID' : Number(addr), 'stateBase': this.config.stateBase+'_service77', 'device' : 'common', 'delay' : 0, 'active' : true, 'channel' : ctx.channelExt, 'timeout' : this.config.timeout }); await udsWorker.initStates(ctx, 'service77'); await udsWorker.startup(ctx, 'service77'); return udsWorker; } async cmndsLoop(ctx) { if ( (await this.getWorkerOpMode() != 'standby') && (this.cmndsQueue.length > 0) && (await this.getComState() == 0) ) { const cmnd = await this.cmndsQueue.shift(); switch (cmnd.mode) { case 'read': { // ReadByDid await this.readByDid(ctx,cmnd.did); await ctx.log.silly('UDS worker on '+this.config.stateBase+': cmndLoop()->readByDid(): '+String(cmnd.did)); break; } case 'write': case 'write77': { // WriteByDid if (cmnd.mode == 'write77') { await ctx.log.silly('UDS worker on '+this.config.stateBase+': cmndLoop()->writeByDid77(): '+String(cmnd.did)); // Startup UDS worker for service 77 if not already available: const txAddr = Number(this.config.canID)+this.SID77addrOffset; const rxAddr = txAddr + 0x10; if (!ctx.E3UdsSID77Workers[rxAddr]) { ctx.E3UdsSID77Workers[rxAddr] = await this.startupUdsWorkerService77(ctx, txAddr); } await ctx.E3UdsSID77Workers[rxAddr].writeByDid77(ctx,cmnd.did); } else { await this.writeByDid2E(ctx,cmnd.did); } await ctx.log.silly('UDS worker on '+this.config.stateBase+': cmndLoop()->writeByDid(): '+String(cmnd.did)); break; } default: { await ctx.log.error('UDS worker on '+this.config.stateBase+': Received unknown command '+cmnd.mode); } } } } statCommFailed(ctxLocal, did) { const didNo = Number(did); if (didNo in ctxLocal.stat.cntCommFailedPerDid) { ctxLocal.stat.cntCommFailedPerDid[didNo] += 1; } else { ctxLocal.stat.cntCommFailedPerDid[didNo] = 1; } } async onTimeout(ctxGlobal, ctxLocal) { const opMode = await ctxLocal.getWorkerOpMode(); if (['standby','normal','service77'].includes(opMode)) { await ctxGlobal.log.error('UDS timeout on '+ctxLocal.canIDhex+'.'+String(ctxLocal.data.did)); if (opMode == 'service77') { await ctxGlobal.log.error('Write access using SID 0x77 failed. Hint: This service is available on internal and master bus only!'); } } ctxLocal.stat.cntCommTimeout += 1; ctxLocal.statCommFailed(ctxLocal, ctxLocal.data.did); if (ctxLocal.callback) await ctxLocal.callback(ctxGlobal, ctxLocal, ['timeout', {'did':ctxLocal.data.did,'didInfo':{'id':'','len':0},'val':''}]); // Store statistics to update timeout count (but do not include the event in the time values): ctxLocal.storage.storeStatistics(ctxGlobal, ctxLocal, false); await ctxLocal.setDidDone(ctxGlobal, 0); } async onUdsStateChange(ctx, ctxWorker, id, state) { // Change of UDS Writables // ======================= if (id.includes(this.storage.storageDids.didsWritablesId)) { // User requests change of UDS writables await ctx.log.info('User requested change of UDS dids writable on '+this.config.stateBase); await this.storage.storageDids.readKnownDids(ctx, await this.getWorkerOpMode()); await ctx.setStateAsync(id, { val: state.val, ack: true }); // Acknowlegde user command return; } // Change of UDS device specific datapoint definition // ================================================== if (id.includes(this.storage.storageDids.didsSpecId)) { // User requests change of UDS device specific datapoint definition await ctx.log.info('User requested change of UDS device specific datapoint definition on '+this.config.stateBase); await this.storage.storageDids.readKnownDids(ctx, await this.getWorkerOpMode()); await ctx.setStateAsync(id, { val: state.val, ack: true }); // Acknowlegde user command return; } // User command ReadByDid // ====================== if (id.includes(this.userReadByDidId)) { // User requests ReadByDid try { const dids = JSON.parse(state.val); await ctx.log.debug('User command UDS ReadByDid on '+this.config.stateBase+'. Dids='+JSON.stringify(dids)); await this.pushCmnd(ctx, 'read', dids); await ctx.setStateAsync(id, { val: JSON.stringify(dids), ack: true }); // Acknowlegde user command } catch(e) { ctx.log.error('ReadByDid(): Parsing of list of DIDs failed on '+this.config.stateBase+'; err='+JSON.stringify(e)+' - You have to provide a list of numerical values.'); } return; } // User command WriteByDid // ======================= const dcs = await ctx.idToDCS(id); // Get device, channel and state id if (!(['json','raw','tree'].includes(dcs.channel))) { // State other than datapoint was stored => no action return; } if (dcs.state.length < 6) { // Implausible state id ctx.log.warn('User command UDS WriteByDid on '+this.config.stateBase+': Could not evaluate state change on id '+id); return; } const did = Number(dcs.state.slice(0,4)); if (!(did in this.storage.storageDids.didsWritable)) { ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+'.'+String(did)+': Writing not allowed on this did. Pls. refer to README for further informations.'); return; } await ctx.log.debug('User command UDS WriteByDid on '+this.config.stateBase+'.'+String(did)); //await ctx.log.debug(JSON.stringify(dcs)+' did='+String(did)+' id='+id+' state='+JSON.stringify(state)); let byteArr=null; // Encoded data let lenBaseId; // Index of start of did state id (did_name ...) in full state id (e3oncan ...) switch (dcs.channel) { case 'json': // Change in json data try { byteArr = await this.storage.encodeDataCAN(ctx, this, did, await JSON.parse(state.val)); if (byteArr) { await this.pushCmnd(ctx, 'write', [[did,byteArr]]); ctx.setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms } else { ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.'); } } catch(e) { ctx.log.error('WriteByDid(): Encoding of data failed on '+this.config.stateBase+'.'+String(did)+'; err='+JSON.stringify(e)); } break; case 'raw': // Change in raw data try { byteArr = this.storage.storageDids.toByteArray(await JSON.parse(state.val)); if (byteArr) { await this.pushCmnd(ctx, 'write', [[did,byteArr]]); ctx.setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms } else { ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.'); } } catch(e) { ctx.log.error('WriteByDid(): Encoding of data failed on '+this.config.stateBase+'.'+String(did)+'; err='+JSON.stringify(e)+' - You have to provide JSON formatted data.'); } break; case 'tree': // Change in tree data lenBaseId = ctx.namespace.length+dcs.device.length+dcs.channel.length+dcs.state.length+3; if (id.length == lenBaseId) { // Scalar value w/o sub structure try { byteArr = await this.storage.encodeDataCAN(ctx, this, did, await JSON.parse(state.val)); if (byteArr) { await this.pushCmnd(ctx, 'write', [[did,byteArr]]); ctx.setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms } else { ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.'); } } catch(e) { ctx.log.error('WriteByDid(): Encoding of data failed on '+this.config.stateBase+'.'+String(did)+'; err='+JSON.stringify(e)); } break; } // Build json object for complete object tree of changed did: await ctx.getStatesOf(dcs.device,dcs.device+'.'+dcs.channel,async function(err,obj) { // Get all states for changed device.channel function insertDictSubVal(dict, keyArr, val) { const listLabels = ['ListEntries','Schedules','TopologyElement']; if (keyArr.length == 1) { const key = keyArr[0]; if (listLabels.includes(key)) dict[key].push(val); else dict[key] = val; } else { const key = keyArr.shift(); if (!(key in dict)) if (listLabels.includes(key)) dict[key] = []; else dict[key] = {}; insertDictSubVal(dict[key],keyArr, val); } } const treeDict = {}; for (const st of Object.values(obj)) { if (st._id.includes(dcs.state)) { const label = st._id.slice(lenBaseId+1); const val = await JSON.parse((await (ctx.getStateAsync(st._id.slice(ctx.namespace.length+1)))).val); insertDictSubVal(treeDict, label.split('.'), val); } } try { byteArr = await ctxWorker.storage.encodeDataCAN(ctx, ctxWorker, did, treeDict); if (byteArr) { await ctxWorker.pushCmnd(ctx, 'write', [[did,byteArr]]); ctx.setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,ctxWorker,did); // Read value after 2500 ms } else { ctx.log.error('User command UDS WriteByDid on '+ctxWorker.config.stateBase+': Encoding of data failed.'); } } catch(e) { ctx.log.error('WriteByDid(): Encoding of data failed on '+ctxWorker.config.stateBase+'.'+String(did)+'; err='+JSON.stringify(e)); } }); break; default: ctx.log.warn('User command UDS WriteByDid on '+this.config.stateBase+': Could not evaluate state change on id '+id); } } initialRequestReadSF(did) { return [this.readByDidProt.PCI, this.readByDidProt.SIDtx,((did >> 8) & 0xFF),(did & 0xFF),0x00,0x00,0x00,0x00]; } initialRequestWrite(did, valRaw, len, prot) { let frame; if (len <= 4) { // Single frame communication frame = [prot.PCI+len+3, prot.SIDtx,((did >> 8) & 0xFF),(did & 0xFF),0x00,0x00,0x00,0x00]; for (let i=0; i<len; i++) { frame[i+4] = valRaw[i]; } } else { // Multi frame communication frame = [prot.PCI+0x10, len+3, prot.SIDtx,((did >> 8) & 0xFF),(did & 0xFF),0x00,0x00,0x00]; for (let i=0; i<3; i++) { frame[i+5] = valRaw[i]; } } return(frame); } canMessage(canID, frame) { return { id: canID,ext: false, rtr: false,data: Buffer.from(frame) }; } async sendFrame(ctx, frame) { await this.config.channel.send(this.canMessage(this.config.canID,frame)); } async readByDid(ctx, did) { if (await this.getWorkerOpMode() == 'standby') { ctx.log.warn('UDS worker warning on '+this.config.stateBase+': Could not execute ReadByDid() for '+String(this.canIDhex)+'.'+String(did)+' due to opMode == standby.'); return; } const state = await this.getComState(); if (state != 0) { await ctx.log.warn('UDS worker warning on '+this.config.stateBase+': ReadByDid(): state '+this.states[state]+' != standby when called! Did '+String(this.canIDhex)+'.'+String(did)+'; Retry issued.'); await this.pushCmnd(ctx, 'read', [did]); return; } this.stat.cntCommTotal += 1; await this.setDidStart(ctx, did, 'read', 0); await this.sendFrame(ctx, await this.initialRequestReadSF(did)); await ctx.log.silly('UDS worker on '+this.config.stateBase+': ReadByDid(): '+String(this.canIDhex)+'.'+String(did)); } async writeByDid2E(ctx, didArr) { if (this.stat.state != 'active') { ctx.log.warn('UDS worker warning on '+this.config.stateBase+': Could not execute WriteByDid() for '+String(this.canIDhex)+'.'+JSON.stringify(didArr)+' due to state != active.'); return; } const did=didArr[0]; const valRaw=didArr[1]; const len=valRaw.length; this.stat.cntCommTotal += 1; this.data.len = len; this.data.valRaw = valRaw; this.data.databytes = valRaw.concat(0x00,0x00,0x00,0x00,0x00,0x00,0x00); // Add padding this.data.did = did; this.data.txPos = 3; this.data.D0 = 0x21; this.writeProt = this.writeByDidProt; await this.setDidStart(ctx, did, 'write', len); await this.sendFrame(ctx, await this.initialRequestWrite(did, valRaw, len, this.writeByDidProt)); await ctx.log.silly('UDS worker on '+this.config.stateBase+': WriteByDid(): '+String(this.canIDhex)+'.'+String(did)+'='+this.storage.storageDids.arr2Hex(valRaw)); } async writeByDid77(ctx, didArr) { if (this.stat.state != 'active') { ctx.log.warn('UDS worker warning on '+this.config.stateBase+': Could not execute WriteByDid() for '+String(this.canIDhex)+'.'+JSON.stringify(didArr)+' due to state != active.'); return; } await ctx.log.debug('User command UDS writeByDid is using SID 0x77'); const did=didArr[0]; const valRaw=didArr[1]; const len=valRaw.length + 6; const len_code = 0xB0 + valRaw.length ; // encode length: 0xb0 + data length const prefix77 = [0x43, 0x01, 0x82, did & 0xFF, (did >> 8) & 0xFF, len_code]; this.stat.cntCommTotal += 1; this.data.len = len; this.data.valRaw = valRaw; this.data.databytes = prefix77.concat(valRaw.concat(0x55,0x55,0x55,0x55,0x55,0x55,0x55)); // Add padding this.data.did = did; this.data.txPos = 3; this.data.D0 = 0x21; this.writeProt = this.writeByDidSID77Prot; await this.setDidStart(ctx, did, 'write', len); await this.sendFrame(ctx, await this.initialRequestWrite(did, this.data.databytes, len, this.writeByDidSID77Prot)); await ctx.log.silly('UDS worker on '+this.config.stateBase+': WriteByDid(): '+String(this.canIDhex)+'.'+String(did)+'='+this.storage.storageDids.arr2Hex(valRaw)); } async msgUds(ctx, msg) { // Typical communication patterns // ReadDataByIdentifier SF: // vcan0 680 [8] 03 22 01 8C 00 00 00 00 // vcan0 690 [8] 05 62 01 8C C2 01 55 55 // ReadDataByIdentifier MF: // vcan0 680 [8] 03 22 01 00 00 00 00 00 // vcan0 690 [8] 10 27 62 01 00 01 02 1F // vcan0 680 [8] 30 00 00 00 00 00 00 00 // vcan0 690 [8] 21 09 14 00 FD 01 01 09 // vcan0 690 [8] 22 C0 00 02 00 64 02 65 // vcan0 690 [8] 23 00 04 00 37 34 37 30 // vcan0 690 [8] 24 36 32 38 32 30 33 33 // vcan0 690 [8] 25 30 37 31 32 38 55 55 // WriteDataByIdentifier SF: // vcan0 680 [8] 05 2E 01 8C C2 01 00 00 // vcan0 690 [8] 03 6E 01 8C 55 55 55 55 // WriteDataByIdentifier MF: // vcan0 680 [8] 10 0C 2E 01 A8 E6 00 D2 // vcan0 690 [8] 30 00 50 55 55 55 55 55 // vcan0 680 [8] 21 00 96 00 00 00 00 00 // vcan0 690 [8] 03 6E 01 A8 55 55 55 55 // WriteDataByIdentifier MF using service 77: // vcan0 682 [8] 10 17 77 01 F8 43 01 82 // vcan0 692 [8] 30 00 05 00 00 00 00 00 // vcan0 682 [8] 21 F8 01 BE 64 00 64 00 // vcan0 682 [8] 22 F4 01 58 02 B6 03 00 // vcan0 682 [8] 23 00 26 02 55 55 55 55 // vcan0 692 [8] 04 77 01 F8 44 55 55 55 if (this.stat.state != 'active') return; // Communication allowed only in state 'active' if (this.commBusy) { this.stat.cntTooBusy += 1; if (this.stat.cntTooBusy == 1) ctx.log.warn('UDS worker warning on '+this.config.stateBase+' (0x'+Number(msg.id).toString(16)+'): Evaluation of messages overloaded.'); if ((this.stat.cntTooBusy % 100) == 0) ctx.log.debug('UDS worker warning on '+this.config.stateBase+' (0x'+Number(msg.id).toString(16)+'): Evaluation of messages overloaded. Counter='+String(this.stat.cntTooBusy)); return; } this.commBusy = true; const candata = msg.data.toJSON().data; //ctx.log.debug('UDS worker on '+this.config.stateBase+' ('+this.canIDhex+'): candata: '+this.storage.storageDids.arr2Hex(candata)); switch (await this.getComState()) { case 0: // standby break; case 1: // waitForFFrbd if ( (candata[0] == this.readByDidProt.SIDcf) && (candata[1] == 0x7F) && (candata[2] == this.readByDidProt.SIDtx) ) { // Negative response this.stat.cntCommNR += 1; if (this.callback) { this.callback(ctx, this, ['negative response', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]); } else { ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response reading did '+String(this.data.did)+'. Code=0x'+Number(candata[3]).toString(16)); } await this.setDidDone(ctx, 0); break; } if ( (candata.length == 8) && ((candata[0] >> 4) == 0) && (candata[1] == this.readByDidProt.SIDrx) ) { // Single-frame communication const didRx = candata[3]+256*candata[2]; if (didRx == this.data.did) { // Did does match this.stat.cntCommOk += 1; ctx.log.silly('UDS worker on '+this.config.stateBase+': SF received. candata: '+this.storage.storageDids.arr2Hex(candata)); await this.calcStat(); this.data.len = candata[0]-3; this.data.databytes = candata.slice(4,4+this.data.len); this.storage.decodeDataCAN(ctx, this, this.data.did, this.data.databytes.slice(0,this.data.len)); await this.setDidDone(ctx, 0); break; } else { // Did does not match this.stat.cntCommBadProtocol += 1; this.statCommFailed(this, this.data.did); if (this.callback) { this.callback(ctx, this, ['did mismatch SF', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]); } else { ctx.log.error('UDS worker on '+this.config.stateBase+': Did mismatch MF. Expected='+String(this.data.did)+'; Received='+String(didRx)); } await this.setDidDone(ctx, 1000); break; } } if ( (candata.length == 8) && ((candata[0] >> 4) == 1) && (candata[2] == this.readByDidProt.SIDrx) ) { // Multiframe communication const didRx = candata[4]+256*candata[3]; if (didRx == this.data.did) { // Did does match this.data.len = (candata[0] & 0x0F)*256 + candata[1] - 3; ctx.log.silly('UDS worker on '+this.config.stateBase+': FF received. candata: '+this.storage.storageDids.arr2Hex(candata)); this.data.databytes = candata.slice(5); this.data.D0 = 0x21; this.sendFrame(ctx, this.readByDidProt.FC); // Send request for Consecutive Frames await this.setComState(2); // 'waitForCFrbd' break; } else { // Did does not match this.stat.cntCommBadProtocol += 1; this.statCommFailed(this, this.data.did); await this.calcStat(); if (this.callback) { this.callback(ctx, this, ['did mismatch MF', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]); } else { ctx.log.error('UDS worker on '+this.config.stateBase+': Did mismatch MF. Expected='+String(this.data.did)+'; Received='+String(didRx)); } await this.setDidDone(ctx, 1000); break; } } if (this.callback) { this.callback(ctx, this, ['bad MF frame', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]); } else { ctx.log.error('UDS worker on '+this.config.stateBase+': Bad frame. candata: '+this.storage.storageDids.arr2Hex(candata)); } await this.calcStat(); this.stat.cntCommBadProtocol += 1; this.statCommFailed(this, this.data.did); await this.setDidDone(ctx, 2500); break; case 2: // waitForCFrbd if ( (candata.length == 8) && (candata[0] == this.data.D0) ) { // Correct code for Consecutive Frame ctx.log.silly('UDS worker on '+this.config.stateBase+': CF received. candata: '+this.storage.storageDids.arr2Hex(candata)); this.data.databytes = this.data.databytes.concat(candata.slice(1)); if (this.data.databytes.length >= this.data.len) { // All data received this.stat.cntCommOk += 1; await this.calcStat(); ctx.log.silly('UDS worker on '+this.config.stateBase+': MF completed. candata: '+this.storage.storageDids.arr2Hex(candata)); this.storage.decodeDataCAN(ctx, this, this.data.did, this.data.databytes.slice(0,this.data.len)); await this.setDidDone(ctx, 0); } else { // More data to come this.data.D0 += 1; if (this.data.D0 > 0x2F) this.data.D0 = 0x20; } } else { // Bad CF if (this.callback) { this.callback(ctx, this, ['bad CF frame', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]); } else { ctx.log.error('UDS worker on '+this.config.stateBase+': Bad frame. candata: '+this.storage.storageDids.arr2Hex(candata)); } this.stat.cntCommBadProtocol += 1; this.statCommFailed(this, this.data.did); await this.setDidDone(ctx, 2500); } break; case 3: // waitForFFSFwbd (wait for confirmation) if ( (candata[0] == this.writeProt.SIDcf) && (candata[1] == 0x7F) && (candata[2] == this.writeProt.SIDtx) ) { // Negative response this.stat.cntCommNR += 1; ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response writing did '+String(this.data.did)+'. Code=0x'+Number(candata[3]).toString(16)); if (await this.getWorkerOpMode() == 'normal') { // Give it one more try using service 77 ctx.log.info('Going to try again using SID 0x77 to write data point on '+this.config.stateBase); this.pushCmnd(ctx, 'write77', [[this.data.did, this.data.valRaw]]); } await this.setDidDone(ctx, 100); break; } if ( (candata.length == 8) && (candata[0] == this.writeProt.SIDcf) && (candata[1] == this.writeProt.SIDrx) ) { // Single-frame communication if ( (candata[0] == this.writeByDidSID77Prot.SIDcf) && (candata[4] != 0x44) ) { // Parallel service 77 communication of other client - ignoring break; } const didRx = candata[3]+256*candata[2]; if (didRx == this.data.did) { // Did does match this.stat.cntCommOk += 1; ctx.log.silly('UDS worker on '+this.config.stateBase+': writeByDid SF confirmation received.'); await this.calcStat(); this.storage.storeStatistics(ctx, this, (await this.getWorkerOpMode() == 'service77')); await this.setDidDone(ctx, 0); break; } else { // Did does not match this.stat.cntCommBadProtocol += 1; this.statCommFailed(this, this.data.did); ctx.log.error('UDS worker on '+this.config.stateBase+': Did mismatch writeByDid SF. Expected='+String(this.data.did)+'; Received='+String(didRx)); await this.calcStat(); this.storage.storeStatistics(ctx, this, true); await this.setDidDone(ctx, 1000); break; } } if (await this.getWorkerOpMode() != 'service77') { // Unexpected frame in normal operation. For service 77 ignore other SID 0x77 frames because it's used by vitocal for internal communication as well ctx.log.error('UDS worker on '+this.config.stateBase+': Bad frame for writeByDid SF. candata: '+this.storage.storageDids.arr2Hex(candata)); this.stat.cntCommBadProtocol += 1; this.statCommFailed(this, this.data.did); await this.calcStat(); await this.setDidDone(ctx, 2500); } break; case 4: // waitForFFMFwbd if ( (candata[0] == this.writeProt.SIDcf) && (candata[1] == 0x7F) && (candata[2] == this.writeProt.SIDtx) ) { // Negative response this.stat.cntCommNR += 1; ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response writing did '+String(this.data.did)+'. Code=0x'+Number(candata[3]).toString(16)); if (await this.getWorkerOpMode() == 'normal') { // Give it one more try using service 77 ctx.log.info('Going to try again using SID 0x77 to write data point on '+this.config.stateBase); this.pushCmnd(ctx, 'write77', [[this.data.did, this.data.valRaw]]); } await this.setDidDone(ctx, 100); break; } if ( (candata.length == 8) && (candata[0] == 0x30) && (candata[1] == 0x00) ) { // Multi-frame communication confirmed // Send data in slices of 7 bytes let ST = candata[2]; // Separation Time (ms) if ((ST<20) || (ST>127)) ST=50; // Accept ST 20 .. 127 ms. Default to 50 ms. while (this.data.txPos < this.data.len) { // More data to send await this.sleep(ctx, ST); const frame = [this.data.D0].concat(this.data.databytes.slice(this.data.txPos,this.data.txPos+7)); await this.sendFrame(ctx, frame); this.data.txPos += 7; this.data.D0 += 1; if (this.data.D0 > 0x2f) this.data.D0 = 0x20; } await this.setComState(3); // waitForFFSFwbd (wait for confirmation) break; } if (await this.getWorkerOpMode() != 'service77') { // Unexpected frame in normal operation. For service 77 ignore other SID 0x77 frames because it's used by vitocal for internal communication as well ctx.log.error('UDS worker on '+this.config.stateBase+': Bad frame for writeByDid MF. candata: '+this.storage.storageDids.arr2Hex(candata)); this.stat.cntCommBadProtocol += 1; this.statCommFailed(this, this.data.did); await this.calcStat(); await this.setDidDone(ctx, 2500); } break; default: this.stat.cntCommBadProtocol += 1; this.statCommFailed(this, this.data.did); if (this.callback) { this.callback(ctx, this, ['bad state value', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]); } else { ctx.log.error('UDS worker on '+this.config.stateBase+': Bad state value: '+String(await this.getComState())); } await this.setDidDone(ctx, 2500); } this.commBusy = false; } } module.exports = { uds };