Skip to content

Commit

Permalink
Added UDS command queue and timeout.
Browse files Browse the repository at this point in the history
  • Loading branch information
MyHomeMyData committed Dec 2, 2023
1 parent 429fbea commit c64ab41
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 18 deletions.
117 changes: 102 additions & 15 deletions lib/canUds.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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));
}
Expand All @@ -71,41 +151,43 @@ 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) ) {
// Single-frame communication
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
Expand All @@ -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;
Expand All @@ -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
}
}
}
Expand Down
21 changes: 18 additions & 3 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
}
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -261,13 +266,23 @@ 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})`);
} else {
// 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.
Expand Down

0 comments on commit c64ab41

Please sign in to comment.