iobroker.e3oncan 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/canUds.js ADDED
@@ -0,0 +1,715 @@
1
+ const storage = require('./storage');
2
+
3
+ class scheduleLoop {
4
+ constructor(ctxGlobal, ctxLocal, schedule, dids) {
5
+ this.ctxGlobal = ctxGlobal;
6
+ this.ctxLocal = ctxLocal;
7
+ this.schedule = schedule;
8
+ this.dids = dids;
9
+ this.schedHandle = null;
10
+ }
11
+
12
+ async startSchedule() {
13
+ if (this.schedule == 0) {
14
+ await this.ctxGlobal.log.silly('UDS schedule one time: '+this.ctxLocal.canIDhex+'.'+JSON.stringify(this.dids));
15
+ await this.ctxLocal.pushCmnd(this.ctxGlobal, 'read', this.dids);
16
+ this.schedHandle = null;
17
+ } else {
18
+ this.schedHandle = setInterval(async () => {
19
+ await this.loop();
20
+ }, this.schedule*1000);
21
+ }
22
+ }
23
+
24
+ async stopSchedule() {
25
+ try {
26
+ if (this.schedHandle) await clearInterval(this.schedHandle);
27
+ await this.ctxGlobal.log.silly('UDS schedule stopped: '+String(this.schedule)+' '+this.ctxLocal.canIDhex+'.'+JSON.stringify(this.dids));
28
+ } catch (e) {
29
+ // Dod nothing
30
+ }
31
+ }
32
+
33
+ async loop() {
34
+ await this.ctxGlobal.log.silly('UDS schedule: '+String(this.schedule)+' '+this.ctxLocal.canIDhex+'.'+JSON.stringify(this.dids));
35
+ await this.ctxLocal.pushCmnd(this.ctxGlobal, 'read', this.dids);
36
+ }
37
+ }
38
+
39
+ class uds {
40
+ constructor(config) {
41
+ this.config = config;
42
+ this.config.statId = 'statUDS';
43
+ this.config.worker = 'uds';
44
+ this.storage = new storage.storage(this.config);
45
+ this.states = ['standby','waitForFFrbd','waitForCFrbd','waitForFFSFwbd','waitForFFMFwbd'];
46
+ this.frameFC = [0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00];
47
+ this.readByDidProt = {
48
+ 'idTx' : this.config.canID,
49
+ 'idRx' : Number(this.config.canID) + 0x10,
50
+ 'PCI' : 0x03, // Protocol Control Information
51
+ 'SIDtx' : 0x22, // Service ID transmit
52
+ 'SIDrx' : 0x62, // Service ID receive
53
+ 'SIDnr' : 0x7F, // SID negative response
54
+ 'FC' : [0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00], // Flow Control frame
55
+ };
56
+ this.writeByDidProt = {
57
+ 'idTx' : this.config.canID,
58
+ 'idRx' : Number(this.config.canID) + 0x10,
59
+ 'PCI' : 0x00, // Protocol Control Information = length of data +3
60
+ 'SIDtx' : 0x2E, // Service ID transmit
61
+ 'SIDrx' : 0x6E, // Service ID receive
62
+ 'SIDnr' : 0x7F, // SID negative response
63
+ 'FCrx' : 0x30, // Flow Control ID for MF transfer
64
+ };
65
+ this.data = {
66
+ 'len' : 0,
67
+ 'tsRequest' : 0,
68
+ 'tsReply' : 0,
69
+ 'tsTotal' : 0,
70
+ 'databytes' : [],
71
+ 'did' : 0,
72
+ 'state' : 0,
73
+ 'D0' : 0x21,
74
+ 'txPos' : 0
75
+ };
76
+ this.canIDhex = '0x'+Number(this.config.canID).toString(16);
77
+ this.cmndsQueue = [];
78
+ this.cmndsHandle = null;
79
+ this.cmndsUpdateTime = 40; // Check for new commands (ms)
80
+ this.busy = false; // Worker is busy
81
+ this.commBusy = false; // Communication routine running
82
+ this.schedules = {};
83
+ this.userReadByDidId = this.config.stateBase+'.cmnd.udsReadByDid';
84
+ this.timeoutHandle = null;
85
+ this.callback = null;
86
+ this.coolDownTs = 0; // Earliest time for next communication
87
+ this.stat = {
88
+ state : 'standby',
89
+ cntCommTotal : 0, // Number of startes communications
90
+ cntCommOk : 0, // Number of succesfull communications
91
+ cntCommNR : 0, // Number of communications ending in negative response
92
+ cntCommTimeout : 0, // Number of communications ending in timeout
93
+ cntCommTimeoutPerDid: {}, // Number of communications ending in timeout for specific did
94
+ cntCommBadProtocol : 0, // Number of bad communications, e.g. bad frame
95
+ cntTooBusy : 0, // Number of conflicting calls of msgUds()
96
+ replyTime : {min:this.config.timeout,max:0,mean:0},
97
+ nextTs : 0, // Timestamp for next storage (earliest)
98
+ tsMinStep : 5000 // Minimum time step between storages
99
+ };
100
+ }
101
+
102
+ async initStates(ctx, opMode) {
103
+ await this.storage.initStates(ctx, opMode);
104
+ if (['standby','normal','udsDidScan'].includes(opMode)) {
105
+ await ctx.setObjectNotExistsAsync(this.config.stateBase+'.cmnd', {
106
+ type: 'channel',
107
+ common: {
108
+ name: this.config.stateBase+' commands',
109
+ role: 'device'
110
+ },
111
+ native: {},
112
+ });
113
+ await ctx.setObjectNotExistsAsync(this.userReadByDidId, {
114
+ type: 'state',
115
+ common: {
116
+ name: 'List of dids to be read via UDS ReadByDid. Place command with ack=false.',
117
+ type: 'json',
118
+ role: 'state',
119
+ read: true,
120
+ write: true,
121
+ },
122
+ native: {},
123
+ });
124
+ await ctx.setStateAsync(this.userReadByDidId, { val: JSON.stringify([]), ack: true });
125
+ await this.storage.storeStatistics(ctx, this, true);
126
+ }
127
+ this.stat.state = 'standby';
128
+ }
129
+
130
+ async startup(ctx, opMode) {
131
+ await this.setComState(0);
132
+ await this.setWorkerOpMode(opMode);
133
+ this.stat.state = 'active';
134
+ await this.storage.storeStatistics(ctx, this, true);
135
+ if (opMode == 'normal') {
136
+ for (const sched of Object.values(this.schedules)) {
137
+ // Start schedules on startup and do one-time schedules
138
+ await sched.startSchedule();
139
+ }
140
+ }
141
+ if (opMode == 'normal') {
142
+ await ctx.log.info('UDS worker started on '+this.config.stateBase);
143
+ } else {
144
+ await ctx.log.silly('UDS worker started in mode '+opMode+' on '+this.config.stateBase);
145
+ }
146
+ this.cmndsHandle = setInterval(async () => {
147
+ await this.cmndsLoop(ctx);
148
+ }, this.cmndsUpdateTime);
149
+ }
150
+
151
+ async stop(ctx) {
152
+ try {
153
+ if (this.stat.state == 'stopped') return;
154
+
155
+ this.stat.state = 'stopped';
156
+ const opMode = await this.storage.getOpMode();
157
+
158
+ await this.storage.storeStatistics(ctx, this, true);
159
+ await this.storage.setOpMode('standby');
160
+
161
+ // Stop loops:
162
+ for (const sched of Object.values(this.schedules)) {
163
+ await sched.stopSchedule();
164
+ }
165
+ if (this.cmndsHandle) await clearInterval(this.cmndsHandle);
166
+
167
+ // Stop Timeout:
168
+ if (this.timeoutHandle) await clearTimeout(this.timeoutHandle);
169
+ this.timeoutHandle = null;
170
+
171
+ // Stop worker:
172
+ this.callback = null;
173
+ if (opMode == 'normal') {
174
+ await ctx.unsubscribeStates(ctx.namespace+'.'+this.config.stateBase+'.*');
175
+ await ctx.log.info('UDS worker stopped on '+this.config.stateBase);
176
+ } else {
177
+ await ctx.log.silly('UDS worker stopped in mode '+opMode+' on '+this.config.stateBase);
178
+ }
179
+ } catch (e) {
180
+ await ctx.log.error('UDS worker on '+this.config.stateBase+' could not be stopped. err='+e.message);
181
+ }
182
+ }
183
+
184
+ async setWorkerOpMode(opMode) {
185
+ await this.storage.setOpMode(opMode);
186
+ }
187
+
188
+ async getWorkerOpMode() {
189
+ return this.storage.getOpMode();
190
+ }
191
+
192
+ async getComState() {
193
+ return this.data.state;
194
+ }
195
+
196
+ async setComState(comState) {
197
+ this.data.state = comState;
198
+ }
199
+
200
+ async setDidDone(coolDownTime) {
201
+ // Finalize communication for recent did
202
+ this.coolDownTs = new Date().getTime()+coolDownTime;
203
+ if (this.cmndsQueue.length == 0) this.busy = false;
204
+ await this.setComState(0);
205
+ if (this.timeoutHandle) await clearTimeout(this.timeoutHandle);
206
+ }
207
+
208
+ async setDidStart(ctx, did, mode, len) {
209
+ switch (mode) {
210
+ case 'read':
211
+ await this.setComState(1); // 'waitForFFrbd'
212
+ break;
213
+ case 'write':
214
+ if (len<=4) {
215
+ // Single frame communication
216
+ await this.setComState(3); // 'waitForFFSFwbd'
217
+ } else {
218
+ // Multi frame communication
219
+ await this.setComState(4); // 'waitForFFMFwbd'
220
+ }
221
+ break;
222
+ default:
223
+ ctx.log.warn('UDS worker started on '+this.config.stateBase+': mode '+mode+' not implemented.');
224
+ }
225
+ const tsNow = new Date().getTime();
226
+ const minWaiting = this.coolDownTs - tsNow;
227
+ if (minWaiting > 0) await this.sleep(minWaiting);
228
+ this.busy = true;
229
+ this.timeoutHandle = await setTimeout(this.onTimeout, this.config.timeout, ctx, this);
230
+ this.data.did = did;
231
+ this.data.tsRequest = tsNow;
232
+ }
233
+
234
+ async calcStat() {
235
+ this.data.tsReply = new Date().getTime();
236
+ const rt = this.data.tsReply - this.data.tsRequest;
237
+ this.data.tsTotal += rt;
238
+ if (rt < this.stat.replyTime.min) this.stat.replyTime.min = rt;
239
+ if (rt > this.stat.replyTime.max) this.stat.replyTime.max = rt;
240
+ this.stat.replyTime.mean = Math.round(this.data.tsTotal/this.stat.cntCommOk);
241
+ }
242
+
243
+ async setCallback(callback) {
244
+ this.callback = callback;
245
+ }
246
+
247
+ async addSchedule(ctx, schedule, dids) {
248
+ if (!Object.keys(this.schedules).includes(schedule)) {
249
+ const didsArr = dids.replace(' ','').split(',');
250
+ this.schedules[schedule] = new scheduleLoop(ctx, this, schedule, didsArr.map(function(str) { return parseInt(str); }));
251
+ ctx.log.silly('UDS worker on '+this.config.stateBase+': Added schedule '+String(schedule)+'s '+JSON.stringify(didsArr));
252
+ } else {
253
+ ctx.log.warn('UDS worker warning on '+this.config.stateBase+': Multiple definiton of schedule:'+String(schedule)+'s');
254
+ }
255
+ }
256
+
257
+ async pushCmnd(ctx, mode, dids) {
258
+ await ctx.log.silly('UDS worker on '+this.config.stateBase+': pushCmnd(): '+mode+' '+String(this.canIDhex)+'.'+String(JSON.stringify(dids)));
259
+ if (Array.isArray(dids)) {
260
+ for (const did of Object.values(dids)) {
261
+ await this.cmndsQueue.push({'mode':mode, 'did': did});
262
+ }
263
+ } else {
264
+ 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));
265
+ }
266
+ }
267
+
268
+ sleep(milliseconds) {
269
+ return new Promise(resolve => setTimeout(resolve, milliseconds));
270
+ }
271
+
272
+ async cmndsLoop(ctx) {
273
+ if ( (await this.storage.getOpMode() != 'standby') && (this.cmndsQueue.length > 0) && (await this.getComState() == 0) ) {
274
+ const cmnd = await this.cmndsQueue.shift();
275
+ switch (cmnd.mode) {
276
+ case 'read': {
277
+ // ReadByDid
278
+ await this.readByDid(ctx,cmnd.did);
279
+ await ctx.log.silly('UDS worker on '+this.config.stateBase+': cmndLoop()->readByDid(): '+String(cmnd.did));
280
+ break;
281
+ }
282
+ case 'write': {
283
+ // WriteByDid
284
+ await this.writeByDid(ctx,cmnd.did);
285
+ await ctx.log.silly('UDS worker on '+this.config.stateBase+': cmndLoop()->writeByDid(): '+String(cmnd.did));
286
+ break;
287
+ }
288
+ default: {
289
+ await ctx.log.error('UDS worker on '+this.config.stateBase+': Received unknown command '+cmnd.mode);
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ async onTimeout(ctxGlobal, ctxLocal) {
296
+ const opMode = await ctxLocal.storage.getOpMode();
297
+ if (['standby','normal'].includes(opMode)) {
298
+ await ctxGlobal.log.error('UDS timeout on '+ctxLocal.canIDhex+'.'+String(ctxLocal.data.did));
299
+ }
300
+ ctxLocal.stat.cntCommTimeout += 1;
301
+ const did = await Number(ctxLocal.data.did);
302
+ if (did in ctxLocal.stat.cntCommTimeoutPerDid) {
303
+ ctxLocal.stat.cntCommTimeoutPerDid[did] += 1;
304
+ } else {
305
+ ctxLocal.stat.cntCommTimeoutPerDid[did] = 1;
306
+ }
307
+ if (ctxLocal.callback) await ctxLocal.callback(ctxGlobal, ctxLocal, ['timeout', {'did':ctxLocal.data.did,'didInfo':{'id':'','len':0},'val':''}]);
308
+ await ctxLocal.setDidDone(0);
309
+ }
310
+
311
+ async onUdsStateChange(ctx, ctxWorker, id, state) {
312
+
313
+ // Change of UDS Writables
314
+ // =======================
315
+ if (id.includes(this.storage.storageDids.didsWritablesId)) {
316
+ // User requests change of UDS writables
317
+ await ctx.log.info('User requested change of UDS dids writable on '+this.config.stateBase);
318
+ await this.storage.storageDids.readKnownDids(ctx, await this.storage.getOpMode());
319
+ await ctx.setStateAsync(id, { val: state.val, ack: true }); // Acknowlegde user command
320
+ return;
321
+ }
322
+
323
+ // Change of UDS device specific datapoint definition
324
+ // ==================================================
325
+ if (id.includes(this.storage.storageDids.didsSpecId)) {
326
+ // User requests change of UDS device specific datapoint definition
327
+ await ctx.log.info('User requested change of UDS device specific datapoint definition on '+this.config.stateBase);
328
+ await this.storage.storageDids.readKnownDids(ctx, await this.storage.getOpMode());
329
+ await ctx.setStateAsync(id, { val: state.val, ack: true }); // Acknowlegde user command
330
+ return;
331
+ }
332
+
333
+ // User command ReadByDid
334
+ // ======================
335
+ if (id.includes(this.userReadByDidId)) {
336
+ // User requests ReadByDid
337
+ const dids = JSON.parse(state.val);
338
+ await ctx.log.debug('User command UDS ReadByDid on '+this.config.stateBase+'. Dids='+JSON.stringify(dids));
339
+ await this.pushCmnd(ctx, 'read', dids);
340
+ await ctx.setStateAsync(id, { val: JSON.stringify(dids), ack: true }); // Acknowlegde user command
341
+ return;
342
+ }
343
+
344
+ // User command WriteByDid
345
+ // =======================
346
+ const dcs = await ctx.idToDCS(id); // Get device, channel and state id
347
+ if (dcs.state.length < 6) {
348
+ // Implausible state id
349
+ ctx.log.warn('User command UDS WriteByDid on '+this.config.stateBase+': Could not evaluate state change on id '+id);
350
+ return;
351
+ }
352
+ const did = Number(dcs.state.slice(0,4));
353
+ if (!(did in this.storage.storageDids.didsWritable)) {
354
+ 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.');
355
+ return;
356
+ }
357
+ await ctx.log.debug('User command UDS WriteByDid on '+this.config.stateBase+'.'+String(did));
358
+ //await ctx.log.debug(JSON.stringify(dcs)+' did='+String(did)+' id='+id+' state='+JSON.stringify(state));
359
+ let byteArr=null; // Encoded data
360
+ let lenBaseId; // Index of start of did state id (did_name ...) in full state id (e3oncan ...)
361
+ switch (dcs.channel) {
362
+ case 'json':
363
+ // Change in json data
364
+ byteArr = await this.storage.encodeDataCAN(ctx, this, did, JSON.parse(state.val));
365
+ if (byteArr) {
366
+ await this.pushCmnd(ctx, 'write', [[did,byteArr]]);
367
+ setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms
368
+ } else {
369
+ ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.');
370
+ }
371
+ break;
372
+ case 'raw':
373
+ // Change in raw data
374
+ byteArr = this.storage.toByteArray(JSON.parse(state.val));
375
+ if (byteArr) {
376
+ await this.pushCmnd(ctx, 'write', [[did,byteArr]]);
377
+ setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms
378
+ } else {
379
+ ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.');
380
+ }
381
+ break;
382
+ case 'tree':
383
+ // Change in tree data
384
+
385
+ lenBaseId = ctx.namespace.length+dcs.device.length+dcs.channel.length+dcs.state.length+3;
386
+ if (id.length == lenBaseId) {
387
+ // Scalar value w/o sub structure
388
+ byteArr = await this.storage.encodeDataCAN(ctx, this, did, JSON.parse(state.val));
389
+ if (byteArr) {
390
+ await this.pushCmnd(ctx, 'write', [[did,byteArr]]);
391
+ setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms
392
+ } else {
393
+ ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.');
394
+ }
395
+ break;
396
+ }
397
+
398
+ // Build json object for complete object tree of changed did:
399
+ await ctx.getStatesOf(dcs.device,dcs.device+'.'+dcs.channel,async function(err,obj) {
400
+ // Get all states for changed device.channel
401
+ /*
402
+ // Probalby better solution based on numbers as marker for arrays. But not working yet.
403
+ function isNumber(value) { return /^\d+$/.test(value); }
404
+ function insertDictSubVal(dict, keyArr, isArray, val) {
405
+ if (keyArr.length == 1) {
406
+ const key = keyArr[0];
407
+ if (isArray) dict[key].push(val); else dict[key] = val;
408
+ } else {
409
+ const key = keyArr.shift();
410
+ const nextIsArray = (((keyArr.length) && (isNumber(keyArr[0]))) ? true : false);
411
+ if (!(key in dict)) if (nextIsArray) dict[key] = []; else dict[key] = {};
412
+ insertDictSubVal(dict[key],keyArr, nextIsArray, val);
413
+ }
414
+ }
415
+ */
416
+ function insertDictSubVal(dict, keyArr, val) {
417
+ const listLabels = ['ListEntries','Schedules','TopologyElement'];
418
+ if (keyArr.length == 1) {
419
+ const key = keyArr[0];
420
+ if (listLabels.includes(key)) dict[key].push(val); else dict[key] = val;
421
+ } else {
422
+ const key = keyArr.shift();
423
+ if (!(key in dict)) if (listLabels.includes(key)) dict[key] = []; else dict[key] = {};
424
+ insertDictSubVal(dict[key],keyArr, val);
425
+ }
426
+ }
427
+ const treeDict = {};
428
+ for (const st of Object.values(obj)) {
429
+ if (st._id.includes(dcs.state)) {
430
+ //await ctx.log.debug(st._id.slice(ctx.namespace.length+1));
431
+ const label = st._id.slice(lenBaseId+1);
432
+ const val = await JSON.parse((await (ctx.getStateAsync(st._id.slice(ctx.namespace.length+1)))).val);
433
+ insertDictSubVal(treeDict, label.split('.'), val);
434
+ }
435
+ }
436
+ byteArr = await ctxWorker.storage.encodeDataCAN(ctx, ctxWorker, did, treeDict);
437
+ if (byteArr) {
438
+ await ctxWorker.pushCmnd(ctx, 'write', [[did,byteArr]]);
439
+ setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,ctxWorker,did); // Read value after 2500 ms
440
+ } else {
441
+ ctx.log.error('User command UDS WriteByDid on '+ctxWorker.config.stateBase+': Encoding of data failed.');
442
+ }
443
+ });
444
+ break;
445
+ default:
446
+ ctx.log.warn('User command UDS WriteByDid on '+this.config.stateBase+': Could not evaluate state change on id '+id);
447
+ }
448
+ }
449
+
450
+ initialRequestReadSF(did) {
451
+ return [this.readByDidProt.PCI, this.readByDidProt.SIDtx,((did >> 8) & 0xFF),(did & 0xFF),0x00,0x00,0x00,0x00];
452
+ }
453
+
454
+ initialRequestWrite(did, valRaw, len) {
455
+ let frame;
456
+ if (len <= 4) {
457
+ // Single frame communication
458
+ frame = [this.writeByDidProt.PCI+len+3, this.writeByDidProt.SIDtx,((did >> 8) & 0xFF),(did & 0xFF),0x00,0x00,0x00,0x00];
459
+ for (let i=0; i<len; i++) {
460
+ frame[i+4] = valRaw[i];
461
+ }
462
+ } else {
463
+ // Multi frame communication
464
+ frame = [this.writeByDidProt.PCI+0x10, len+3, this.writeByDidProt.SIDtx,((did >> 8) & 0xFF),(did & 0xFF),0x00,0x00,0x00];
465
+ for (let i=0; i<3; i++) {
466
+ frame[i+5] = valRaw[i];
467
+ }
468
+ }
469
+ return(frame);
470
+ }
471
+
472
+ canMessage(canID, frame) {
473
+ return { id: canID,ext: false, rtr: false,data: Buffer.from(frame) };
474
+ }
475
+
476
+ async sendFrame(ctx, frame) {
477
+ await this.config.channel.send(this.canMessage(this.config.canID,frame));
478
+ }
479
+
480
+ async readByDid(ctx, did) {
481
+ if (await this.storage.getOpMode() == 'standby') {
482
+ ctx.log.warn('UDS worker warning on '+this.config.stateBase+': Could not execute ReadByDid() for '+String(this.canIDhex)+'.'+String(did)+' due to opMode == standby.');
483
+ return;
484
+ }
485
+ const state = await this.getComState();
486
+ if (state != 0) {
487
+ 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.');
488
+ await this.pushCmnd(ctx, 'read', [did]);
489
+ return;
490
+ }
491
+ this.stat.cntCommTotal += 1;
492
+ await this.setDidStart(ctx, did, 'read', 0);
493
+ await this.sendFrame(ctx, await this.initialRequestReadSF(did));
494
+ await ctx.log.silly('UDS worker on '+this.config.stateBase+': ReadByDid(): '+String(this.canIDhex)+'.'+String(did));
495
+ }
496
+
497
+ async writeByDid(ctx, didArr) {
498
+ if (await this.storage.getOpMode() == 'standby') {
499
+ ctx.log.warn('UDS worker warning on '+this.config.stateBase+': Could not execute WriteByDid() for '+String(this.canIDhex)+'.'+JSON.stringify(didArr)+' due to opMode == standby.');
500
+ return;
501
+ }
502
+ const did=didArr[0];
503
+ const valRaw=didArr[1];
504
+ const len=valRaw.length;
505
+ this.stat.cntCommTotal += 1;
506
+ this.data.len = len;
507
+ this.data.databytes = valRaw.concat(0x00,0x00,0x00,0x00,0x00,0x00,0x00); // Add padding
508
+ this.data.did = did;
509
+ this.data.txPos = 3;
510
+ this.data.D0 = 0x21;
511
+ await this.setDidStart(ctx, did, 'write', len);
512
+ await this.sendFrame(ctx, await this.initialRequestWrite(did, valRaw, len));
513
+ await ctx.log.silly('UDS worker on '+this.config.stateBase+': WriteByDid(): '+String(this.canIDhex)+'.'+String(did)+'='+this.storage.arr2Hex(valRaw));
514
+ }
515
+
516
+ async msgUds(ctx, msg) {
517
+
518
+ if (await this.storage.getOpMode() == 'standby') return; // No communication in mode 'standby'
519
+
520
+ if (this.commBusy) {
521
+ this.stat.cntTooBusy += 1;
522
+ if (this.stat.cntTooBusy == 1) ctx.log.warn('UDS worker warning on '+this.config.stateBase+': Evaluation of messages overloaded.');
523
+ return;
524
+ }
525
+
526
+ this.commBusy = true;
527
+
528
+ const candata = msg.data.toJSON().data;
529
+
530
+ switch (await this.getComState()) {
531
+ case 0: // standby
532
+ break;
533
+ case 1: // waitForFFrbd
534
+ if ( (candata[0] == 0x03) && (candata[1] == 0x7F) && (candata[2] == this.readByDidProt.SIDtx) ) {
535
+ // Negative response
536
+ this.stat.cntCommNR += 1;
537
+ if (this.callback) {
538
+ this.callback(ctx, this, ['negative response', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]);
539
+ } else {
540
+ ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response on device '+this.canIDhex+'. Code=0x'+Number(candata[3]).toString(16));
541
+ }
542
+ await this.setDidDone(0);
543
+ break;
544
+ }
545
+ if ( (candata.length == 8) && ((candata[0] >> 4) == 0) && (candata[1] == this.readByDidProt.SIDrx) ) {
546
+ // Single-frame communication
547
+ const didRx = candata[3]+256*candata[2];
548
+ if (didRx == this.data.did) {
549
+ // Did does match
550
+ this.stat.cntCommOk += 1;
551
+ ctx.log.silly('UDS worker on '+this.config.stateBase+': SF received. candata: '+this.storage.arr2Hex(candata));
552
+ await this.calcStat();
553
+ this.data.len = candata[0]-3;
554
+ this.data.databytes = candata.slice(4,4+this.data.len);
555
+ this.storage.decodeDataCAN(ctx, this, this.data.did, this.data.databytes.slice(0,this.data.len));
556
+ await this.setDidDone(0);
557
+ break;
558
+ } else {
559
+ // Did does not match
560
+ this.stat.cntCommBadProtocol += 1;
561
+ if (this.callback) {
562
+ this.callback(ctx, this, ['did mismatch SF', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]);
563
+ } else {
564
+ ctx.log.error('UDS worker on '+this.config.stateBase+': Did mismatch MF. Expected='+String(this.data.did)+'; Received='+String(didRx));
565
+ }
566
+ await this.setDidDone(1000);
567
+ break;
568
+ }
569
+ }
570
+ if ( (candata.length == 8) && ((candata[0] >> 4) == 1) && (candata[2] == this.readByDidProt.SIDrx) ) {
571
+ // Multiframe communication
572
+ const didRx = candata[4]+256*candata[3];
573
+ if (didRx == this.data.did) {
574
+ // Did does match
575
+ this.data.len = (candata[0] & 0x0F)*256 + candata[1] - 3;
576
+ ctx.log.silly('UDS worker on '+this.config.stateBase+': FF received. candata: '+this.storage.arr2Hex(candata));
577
+ this.data.databytes = candata.slice(5);
578
+ this.data.D0 = 0x21;
579
+ this.sendFrame(ctx, this.frameFC); // Send request for Consecutive Frames
580
+ await this.setComState(2); // 'waitForCFrbd'
581
+ break;
582
+ } else {
583
+ // Did does not match
584
+ this.stat.cntCommBadProtocol += 1;
585
+ await this.calcStat();
586
+ if (this.callback) {
587
+ this.callback(ctx, this, ['did mismatch MF', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]);
588
+ } else {
589
+ ctx.log.error('UDS worker on '+this.config.stateBase+': Did mismatch MF. Expected='+String(this.data.did)+'; Received='+String(didRx));
590
+ }
591
+ await this.setDidDone(1000);
592
+ break;
593
+ }
594
+ }
595
+ if (this.callback) {
596
+ this.callback(ctx, this, ['bad MF frame', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]);
597
+ } else {
598
+ ctx.log.error('UDS worker on '+this.config.stateBase+': Bad frame. candata: '+this.storage.arr2Hex(candata));
599
+ }
600
+ await this.calcStat();
601
+ this.stat.cntCommBadProtocol += 1;
602
+ await this.setDidDone(2500);
603
+ break;
604
+
605
+ case 2: // waitForCFrbd
606
+ if ( (candata.length == 8) && (candata[0] == this.data.D0) ) {
607
+ // Correct code for Consecutive Frame
608
+ ctx.log.silly('UDS worker on '+this.config.stateBase+': CF received. candata: '+this.storage.arr2Hex(candata));
609
+ this.data.databytes = this.data.databytes.concat(candata.slice(1));
610
+ if (this.data.databytes.length >= this.data.len) {
611
+ // All data received
612
+ this.stat.cntCommOk += 1;
613
+ await this.calcStat();
614
+ ctx.log.silly('UDS worker on '+this.config.stateBase+': MF completed. candata: '+this.storage.arr2Hex(candata));
615
+ this.storage.decodeDataCAN(ctx, this, this.data.did, this.data.databytes.slice(0,this.data.len));
616
+ await this.setDidDone(0);
617
+ } else {
618
+ // More data to come
619
+ this.data.D0 += 1;
620
+ if (this.data.D0 > 0x2F) this.data.D0 = 0x20;
621
+ }
622
+ } else {
623
+ // Bad CF
624
+ if (this.callback) {
625
+ this.callback(ctx, this, ['bad CF frame', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]);
626
+ } else {
627
+ ctx.log.error('UDS worker on '+this.config.stateBase+': Bad frame. candata: '+this.storage.arr2Hex(candata));
628
+ }
629
+ this.stat.cntCommBadProtocol += 1;
630
+ await this.setDidDone(2500);
631
+ }
632
+ break;
633
+
634
+ case 3: // waitForFFSFwbd
635
+ if ( (candata[0] == 0x03) && (candata[1] == 0x7F) && (candata[2] == this.writeByDidProt.SIDtx) ) {
636
+ // Negative response
637
+ this.stat.cntCommNR += 1;
638
+ ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response on device '+this.canIDhex+'. Code=0x'+Number(candata[3]).toString(16));
639
+ await this.setDidDone(0);
640
+ break;
641
+ }
642
+ if ( (candata.length == 8) && (candata[0] == 0x03) && (candata[1] == this.writeByDidProt.SIDrx) ) {
643
+ // Single-frame communication
644
+ const didRx = candata[3]+256*candata[2];
645
+ if (didRx == this.data.did) {
646
+ // Did does match
647
+ this.stat.cntCommOk += 1;
648
+ ctx.log.silly('UDS worker on '+this.config.stateBase+': writeByDid SF confirmation received.');
649
+ await this.calcStat();
650
+ this.storage.storeStatistics(ctx, this, false);
651
+ await this.setDidDone(0);
652
+ break;
653
+ } else {
654
+ // Did does not match
655
+ this.stat.cntCommBadProtocol += 1;
656
+ ctx.log.error('UDS worker on '+this.config.stateBase+': Did mismatch writeByDid SF. Expected='+String(this.data.did)+'; Received='+String(didRx));
657
+ await this.calcStat();
658
+ this.storage.storeStatistics(ctx, this, true);
659
+ await this.setDidDone(1000);
660
+ break;
661
+ }
662
+ }
663
+ ctx.log.error('UDS worker on '+this.config.stateBase+': Bad frame for writeByDid SF. candata: '+this.storage.arr2Hex(candata));
664
+ this.stat.cntCommBadProtocol += 1;
665
+ await this.calcStat();
666
+ await this.setDidDone(2500);
667
+ break;
668
+
669
+ case 4: // waitForFFFwbd
670
+ if ( (candata[0] == 0x03) && (candata[1] == 0x7F) && (candata[2] == this.writeByDidProt.SIDtx) ) {
671
+ // Negative response
672
+ this.stat.cntCommNR += 1;
673
+ ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response on device '+this.canIDhex+'. Code=0x'+Number(candata[3]).toString(16));
674
+ await this.setDidDone(0);
675
+ break;
676
+ }
677
+ if ( (candata.length == 8) && (candata[0] == 0x30) && (candata[1] == 0x00) ) {
678
+ // Multi-frame communication confirmed
679
+ // Send data in slices of 7 bytes
680
+ let ST = candata[2]; // Separation Time (ms)
681
+ if ((ST<20) || (ST>127)) ST=50; // Accept ST 20 .. 127 ms. Default to 50 ms.
682
+ while (this.data.txPos < this.data.len) {
683
+ // More data to send
684
+ await this.sleep(ST);
685
+ const frame = [this.data.D0].concat(this.data.databytes.slice(this.data.txPos,this.data.txPos+7));
686
+ await this.sendFrame(ctx, frame);
687
+ this.data.txPos += 7;
688
+ this.data.D0 += 1;
689
+ if (this.data.D0 > 0x2f) this.data.D0 = 0x20;
690
+ }
691
+ await this.setComState(3); // waitForFFSFwbd (wait for confirmation)
692
+ break;
693
+ }
694
+ ctx.log.error('UDS worker on '+this.config.stateBase+': Bad frame for writeByDid MF. candata: '+this.storage.arr2Hex(candata));
695
+ this.stat.cntCommBadProtocol += 1;
696
+ await this.calcStat();
697
+ await this.setDidDone(2500);
698
+ break;
699
+
700
+ default:
701
+ this.stat.cntCommBadProtocol += 1;
702
+ if (this.callback) {
703
+ this.callback(ctx, this, ['bad state value', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]);
704
+ } else {
705
+ ctx.log.error('UDS worker on '+this.config.stateBase+': Bad state value: '+String(await this.getComState()));
706
+ }
707
+ await this.setDidDone(2500);
708
+ }
709
+ this.commBusy = false;
710
+ }
711
+ }
712
+
713
+ module.exports = {
714
+ uds
715
+ };