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/LICENSE +21 -0
- package/README.md +129 -0
- package/admin/e3oncan.png +0 -0
- package/admin/e3oncan_small.png +0 -0
- package/admin/i18n/de/translations.json +60 -0
- package/admin/i18n/en/translations.json +60 -0
- package/admin/i18n/es/translations.json +62 -0
- package/admin/i18n/fr/translations.json +62 -0
- package/admin/i18n/it/translations.json +62 -0
- package/admin/i18n/nl/translations.json +62 -0
- package/admin/i18n/pl/translations.json +62 -0
- package/admin/i18n/pt/translations.json +62 -0
- package/admin/i18n/ru/translations.json +62 -0
- package/admin/i18n/uk/translations.json +62 -0
- package/admin/i18n/zh-cn/translations.json +62 -0
- package/admin/jsonConfig.json +594 -0
- package/admin/udsDevices.json +37 -0
- package/io-package.json +164 -0
- package/lib/adapter-config.d.ts +19 -0
- package/lib/canCollect.js +199 -0
- package/lib/canUds.js +715 -0
- package/lib/codecs.js +709 -0
- package/lib/didsE3.json +29025 -0
- package/lib/didsE380.json +275 -0
- package/lib/didsE3Writables.json +396 -0
- package/lib/enums.js +2223 -0
- package/lib/storage.js +396 -0
- package/lib/udsScan.js +353 -0
- package/main.js +490 -0
- package/package.json +80 -0
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
|
+
};
|