From c64ab41d8330e4666bccb9b68f32d34b635e98c5 Mon Sep 17 00:00:00 2001 From: MyHomeMyData Date: Sat, 2 Dec 2023 12:42:42 +0100 Subject: [PATCH] Added UDS command queue and timeout. --- lib/canUds.js | 117 +++++++++++++++++++++++++++++++++++++++++++------- main.js | 21 +++++++-- 2 files changed, 120 insertions(+), 18 deletions(-) diff --git a/lib/canUds.js b/lib/canUds.js index 201d834..0b24424 100644 --- a/lib/canUds.js +++ b/lib/canUds.js @@ -24,7 +24,10 @@ class uds { 'state' : this.states[0], 'D0' : 0x21 }; - this.cmndQueueId = 'uds.0x'+Number(this.config.canID).toString(16)+'.cmndQueue'; + this.canIDhex = '0x'+Number(this.config.canID).toString(16); + this.cmndQueueId = 'admin.uds.'+this.canIDhex+'.cmndQueue'; + this.timeoutId = 'admin.uds.'+this.canIDhex+'.timeout'; + this.userDidsToReadId = 'uds.'+this.canIDhex+'.didsToRead'; } async initStates(ctx) { @@ -40,13 +43,88 @@ class uds { }, native: {}, }); - await ctx.setStateAsync(this.cmndQueueId, JSON.stringify([]), true); + await ctx.setStateAsync(this.cmndQueueId, { val: JSON.stringify([]), ack: true }); + await ctx.setObjectNotExistsAsync(this.timeoutId, { + type: 'state', + common: { + name: 'Timeout control for UDS communication. Gets null in case of timeout.', + type: 'boolean', + role: 'state', + read: true, + write: true, + }, + native: {}, + }); + await ctx.setStateAsync(this.timeoutId, { val: false, ack: true }); + ctx.subscribeStates(this.timeoutId); + await ctx.setObjectNotExistsAsync(this.userDidsToReadId, { + 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.userDidsToReadId, { val: JSON.stringify([]), ack: true }); + ctx.subscribeStates(this.userDidsToReadId); } - pushCmnd(ctx, mode, dids) { - const cmnds = JSON.parse(ctx.getState(this.cmndQueueId).val); + async pushCmnd(ctx, mode, dids) { + const obj = await ctx.getStateAsync(this.cmndQueueId); + const cmnds = JSON.parse(obj.val); cmnds.push({'mode':mode, 'dids': dids}); - ctx.setState(JSON.stringify(cmnds), true); + await ctx.setStateAsync(this.cmndQueueId, {val: JSON.stringify(cmnds), ack: true}); + } + + sleep(milliseconds) { + return new Promise(resolve => setTimeout(resolve, milliseconds)); + } + + async cmndLoop(ctx) { + // eslint-disable-next-line no-constant-condition + while (true) { + const obj = await ctx.getStateAsync(this.cmndQueueId); + const cmnds = JSON.parse(obj.val); + if ( (cmnds.length > 0) && (this.data.state == this.states[0]) ) { + const cmnd = cmnds.shift(); + if (cmnd.mode == 'read') { + for (const did of Object.values(cmnd.dids)) { + // Do ReadByDid for each did + await this.readByDid(ctx,did); + ctx.log.silly('cmndLoop()->ReadByDid: '+String(did)); + while (this.data.state != this.states[0]) { + await this.sleep(10); + } + await this.sleep(10); + } + } else { + ctx.log.error('Only UDS mode "readByDid" implemented yet.'); + } + await ctx.setStateAsync(this.cmndQueueId, {val: JSON.stringify(cmnds), ack: true}); + } + await this.sleep(1000); + } + } + + async onTimeoutChange(ctx, obj) { + if (!obj) { + ctx.log.error('UDS timeout on device '+this.canIDhex+'. Did='+String(this.data.did)); + await ctx.setStateAsync(this.timeoutId, { val: false, ack: true }); + this.data.state = this.states[0]; // Reset communication + } + } + + async onUserReadDidsChange(ctx, state) { + const dids = JSON.parse(state.val); + if (!state.ack) { + // Execute user command + ctx.log.debug('UDS user command on device '+this.canIDhex+'. Dids='+String(dids)); + await this.pushCmnd(ctx, 'read', dids); + await ctx.setStateAsync(this.userDidsToReadId, { val: JSON.stringify(dids), ack: true }); // Acknowlegde user command + } } initialRequestSF(did) { @@ -63,6 +141,8 @@ class uds { async readByDid(ctx, did) { this.data.state = this.states[1]; // 'waitForFF' + await ctx.setStateAsync(this.timeoutId, + { val: false, ack: true, expire: this.config.timeout }); // Start Timeout monitoring this.data.did = did; await this.sendFrame(this.initialRequestSF(did)); } @@ -71,14 +151,13 @@ class uds { const candata = msg.data.toJSON().data; const canid = msg.id; - //ctx.log.debug('msgUds: '+String(canid)+' '+JSON.stringify(candata)); - switch (this.data.state) { case 'waitForFF': if ( (candata[0] == 0x03) && (candata[1] == 0x7F) && (candata[2] == this.readByDidProt.SIDtx) ) { // Negative response this.data.state = this.states[0]; - ctx.log.error('msgUds(): Negative response. Code=0x'+Number(candata[3]).toString(16)); + await ctx.setStateAsync(this.timeoutId, { val: false, ack: true }); // Stop timeout monitoring + ctx.log.error('msgUds(): Negative response on device '+this.canIDhex+'. Code=0x'+Number(candata[3]).toString(16)); break; } if ( (candata.length == 8) && ((candata[0] >> 4) == 0) && (candata[1] == this.readByDidProt.SIDrx) ) { @@ -86,26 +165,29 @@ class uds { const didRx = candata[3]+256*candata[2]; if (didRx == this.data.did) { // Did does match + ctx.log.silly('msgUds SF: '+this.canIDhex+' '+JSON.stringify(candata)); this.data.len = candata[0]-3; this.data.databytes = candata.slice(4,4+this.data.len); await this.storage.decodeDataCAN(ctx, this.data.did, this.data.databytes.slice(0,this.data.len)); this.data.state = this.states[0]; // 'standby' + await ctx.setStateAsync(this.timeoutId, { val: false, ack: true }); // Stop timeout monitoring break; } else { // Did does not match this.data.state = this.states[0]; // 'standby' - ctx.log.error('msgUds(): Did mismatch'); + await ctx.setStateAsync(this.timeoutId, { val: false, ack: true }); // Stop timeout monitoring + ctx.log.error('msgUds(): Did mismatch on device '+this.canIDhex); break; } } if ( (candata.length == 8) && ((candata[0] >> 4) == 1) && (candata[2] == this.readByDidProt.SIDrx) ) { // Multiframe communication - ctx.log.debug('msgUds FF: '+String(canid)+' '+JSON.stringify(candata)); + ctx.log.silly('msgUds FF: '+this.canIDhex+' '+JSON.stringify(candata)); 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.debug('msgUds FF: data.len='+String(this.data.len)); + ctx.log.silly('msgUds FF: data.len='+String(this.data.len)); this.data.databytes = candata.slice(5,4+this.data.len); this.data.D0 = 0x21; await this.sendFrame(this.frameFC); // Send request for Consecutive Frames @@ -114,22 +196,26 @@ class uds { } else { // Did does not match this.data.state = this.states[0]; - ctx.log.error('msgUds(): Did mismatch. Expected='+String(this.data.did)+'; Received='+String(didRx)); + await ctx.setStateAsync(this.timeoutId, { val: false, ack: true }); // Stop timeout monitoring + ctx.log.error('msgUds(): Did mismatch on device '+this.canIDhex+'. Expected='+String(this.data.did)+'; Received='+String(didRx)); break; } } - ctx.log.error('msgUds(): Bad frame: '+JSON.stringify(candata)); + ctx.log.error('msgUds(): Bad frame on device '+this.canIDhex+': '+JSON.stringify(candata)); this.data.state = this.states[0]; // 'standby' + await ctx.setStateAsync(this.timeoutId, { val: false, ack: true }); // Stop timeout monitoring break; case 'waitForCF': if ( (candata.length == 8) && (candata[0] == this.data.D0) ) { // Correct code for Consecutive Frame - ctx.log.debug('msgUds CF: '+String(canid)+' '+JSON.stringify(candata)); + ctx.log.silly('msgUds CF: '+this.canIDhex+' '+JSON.stringify(candata)); this.data.databytes = this.data.databytes.concat(candata.slice(1)); if (this.data.databytes.length >= this.data.len) { // All data received this.storage.decodeDataCAN(ctx, this.data.did, this.data.databytes.slice(0,this.data.len)); + this.data.state = this.states[0]; + await ctx.setStateAsync(this.timeoutId, { val: false, ack: true }); // Stop timeout monitoring } else { // More data to come this.data.D0 += 1; @@ -141,8 +227,9 @@ class uds { break; default: - ctx.log.error('msgUds(): Bad state value'); + ctx.log.error('msgUds(): Bad state value on device '+this.canIDhex); this.data.state = this.states[0]; + await ctx.setStateAsync(this.timeoutId, { val: false, ack: true }); // Stop timeout monitoring } } } diff --git a/main.js b/main.js index 8528546..27bccfe 100644 --- a/main.js +++ b/main.js @@ -127,7 +127,9 @@ class E3oncan extends utils.Adapter { 'delay': 0, 'doTree': dev.uds_tree_states, 'doJSON': dev.uds_json_states, - 'channel': this.channelExt}); + 'channel': this.channelExt, + 'timeout': 2 // Commuication timeout (s) + }); this.E3Uds.push(Uds); await Uds.initStates(this); } } @@ -179,10 +181,13 @@ class E3oncan extends utils.Adapter { } if (this.E3Uds[0]) { - //await this.E3Uds[0].readByDid(this, 2346); - await this.E3Uds[0].readByDid(this, 256); + await this.E3Uds[0].pushCmnd(this, 'read', [256]); + await this.E3Uds[0].pushCmnd(this, 'read', [256,269,2346]); + this.E3Uds[0].cmndLoop(this); } + this.log.debug('onReady(): All done.'); + // In order to get state updates, you need to subscribe to them. The following line adds a subscription for our variable we have created above. // this.subscribeStates('testVariable'); // You can also add a subscription for multiple states. The following line watches all states starting with "lights." @@ -261,6 +266,7 @@ class E3oncan extends utils.Adapter { * @param {ioBroker.State | null | undefined} state */ onStateChange(id, state) { + /* if (state) { // The state was changed this.log.info(`state ${id} changed: ${state.val} (ack = ${state.ack})`); @@ -268,6 +274,15 @@ class E3oncan extends utils.Adapter { // The state was deleted this.log.info(`state ${id} deleted`); } + */ + for (const dev of Object.values(this.E3Uds)) { + if ( (dev) && (id.includes(dev.timeoutId)) ) { + dev.onTimeoutChange(this, state); + } + if ( (dev) && (id.includes(dev.userDidsToReadId)) ) { + dev.onUserReadDidsChange(this, state); + } + } } // If you need to accept messages in your adapter, uncomment the following block and the corresponding line in the constructor.