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/storage.js ADDED
@@ -0,0 +1,396 @@
1
+
2
+ const E3 = require('./codecs');
3
+ const E3DidsDict = require('./didsE3.json');
4
+ const E380DidsDict = require('./didsE380.json');
5
+
6
+ class storageDids {
7
+ constructor(config) {
8
+ this.config = config;
9
+ this.didsWritablesId = 'udsDidsWritable';
10
+ this.didsCommonId = 'udsDidsCommon';
11
+ this.didsSpecId = 'udsDidsSpecific';
12
+ this.didsWritable = {};
13
+ this.didsDictE3 = {}; // Common dids imported from project open3e
14
+ this.didsDictDevCom = {}; // Dids of this device matching the E3 common list
15
+ this.didsDictDevSpec = {}; // Dids specific for this device
16
+ this.dids = {}; // Consolidated list of dids available for this device
17
+ this.didsDevSpecAvail= false; // true, if device specific dids are available
18
+ }
19
+ async initStates(ctx, opMode) {
20
+ if (['standby','normal','udsDidScan'].includes(opMode)) {
21
+ if (this.config.device != 'e380') {
22
+ await ctx.setObjectNotExistsAsync(this.config.stateBase+'.info.'+this.didsWritablesId, {
23
+ type: 'state',
24
+ common: {
25
+ name: this.config.stateBase+' list of datapoints writable via WriteByDid',
26
+ role: 'state',
27
+ type: 'json',
28
+ read: true,
29
+ write: true,
30
+ def: JSON.stringify({})
31
+ },
32
+ native: {},
33
+ });
34
+
35
+ await ctx.setObjectNotExistsAsync(this.config.stateBase+'.info.'+this.didsCommonId, {
36
+ type: 'state',
37
+ common: {
38
+ name: this.config.stateBase+' all available datapoints',
39
+ role: 'state',
40
+ type: 'json',
41
+ read: true,
42
+ write: true,
43
+ def: JSON.stringify({})
44
+ },
45
+ native: {},
46
+ });
47
+
48
+ await ctx.setObjectNotExistsAsync(this.config.stateBase+'.info.'+this.didsSpecId, {
49
+ type: 'state',
50
+ common: {
51
+ name: this.config.stateBase+' available datapoints specific to this device',
52
+ role: 'state',
53
+ type: 'json',
54
+ read: true,
55
+ write: true,
56
+ def: JSON.stringify({})
57
+ },
58
+ native: {},
59
+ });
60
+ }
61
+ }
62
+ }
63
+ async mergeDids(didsCommon, didsDevSpecific) {
64
+ const dids = await JSON.parse(JSON.stringify(didsCommon));
65
+ for (const [id, did] of Object.entries(didsDevSpecific)) {
66
+ dids[id] = did;
67
+ }
68
+ return(dids);
69
+ }
70
+ async readKnownDids(ctx, opMode) {
71
+ // Read common and devive specific dids list known from dids scan
72
+ // If scanned dids are avalilable: return complete list of dids for this device
73
+ // else return list of common dids for all devices and list of writable dids
74
+ if (this.config.device != 'e380') {
75
+ if (opMode != 'udsDevScan') {
76
+ try {
77
+ const baseId = this.config.stateBase+'.info.';
78
+ this.didsWritable = await JSON.parse((await ctx.getStateAsync(baseId+this.didsWritablesId)).val);
79
+ this.didsDictDevCom = await JSON.parse((await ctx.getStateAsync(baseId+this.didsCommonId)).val);
80
+ this.didsDictDevSpec = await JSON.parse((await ctx.getStateAsync(baseId+this.didsSpecId)).val);
81
+ this.didsDevSpecAvail= true;
82
+ } catch(e) {
83
+ // Device specific data not available yet
84
+ this.didsWritable = {};
85
+ this.didsDictDevCom = E3DidsDict;
86
+ this.didsDictDevSpec = {};
87
+ this.didsDevSpecAvail= false;
88
+ }
89
+ if (Object.keys(this.didsDictDevCom).length == 0) {
90
+ // No dids scan results available yet
91
+ this.didsDictDevCom = E3DidsDict;
92
+ }
93
+ } else {
94
+ // UDS device scan
95
+ this.didsWritable = {};
96
+ this.didsDictDevCom[ctx.udsDidForScan] = E3DidsDict[ctx.udsDidForScan];
97
+ this.didsDictDevSpec = {};
98
+ }
99
+ } else {
100
+ this.didsWritable = {};
101
+ this.didsDictDevCom = {};
102
+ this.didsDictDevSpec = E380DidsDict;
103
+ }
104
+ if (opMode == 'udsDidScan') {
105
+ this.didsDictDevCom = {};
106
+ this.dids = E3DidsDict;
107
+ } else {
108
+ this.dids = await this.mergeDids(this.didsDictDevCom,this.didsDictDevSpec);
109
+ }
110
+ }
111
+ async storeKnownDids(ctx) {
112
+ if (this.config.device != 'e380') {
113
+ const baseId = this.config.stateBase+'.info.';
114
+ await ctx.setStateAsync(baseId+this.didsWritablesId, {val: JSON.stringify(this.didsWritable), ack: true});
115
+ await ctx.setStateAsync(baseId+this.didsCommonId, {val: JSON.stringify(this.didsDictDevCom), ack: true});
116
+ await ctx.setStateAsync(baseId+this.didsSpecId, {val: JSON.stringify(this.didsDictDevSpec), ack: true});
117
+ }
118
+ }
119
+ }
120
+
121
+ class storage {
122
+ constructor(config) {
123
+ this.config = config;
124
+ this.storageDids = new storageDids({
125
+ stateBase:this.config.stateBase,
126
+ device:this.config.device
127
+ });
128
+ this.opModes = ['standby','udsDevScan','udsDidScan','normal','TEST'];
129
+ this.opMode = this.opModes[0];
130
+ this.udsScanResult = null;
131
+ }
132
+ async initStates(ctx, opMode) {
133
+ await this.setOpMode(opMode);
134
+ if (opMode != 'udsDevScan') {
135
+ await ctx.setObjectNotExistsAsync(this.config.stateBase, {
136
+ type: 'device',
137
+ common: {
138
+ name: this.config.stateBase,
139
+ role: 'device'
140
+ },
141
+ native: {},
142
+ });
143
+
144
+ await ctx.setObjectNotExistsAsync(this.config.stateBase+'.info', {
145
+ type: 'channel',
146
+ common: {
147
+ name: this.config.stateBase+' informations',
148
+ role: 'channel'
149
+ },
150
+ native: {},
151
+ });
152
+
153
+ await ctx.setObjectNotExistsAsync(this.config.stateBase+'.info.'+this.config.statId, {
154
+ type: 'state',
155
+ common: {
156
+ name: this.config.stateBase+' statistics about communication',
157
+ role: 'state',
158
+ type: 'json',
159
+ read: true,
160
+ write: true,
161
+ def: JSON.stringify({})
162
+ },
163
+ native: {},
164
+ });
165
+
166
+ await this.storageDids.initStates(ctx, opMode);
167
+
168
+ await ctx.setObjectNotExistsAsync(this.config.stateBase+'.json', {
169
+ type: 'channel',
170
+ common: {
171
+ name: this.config.stateBase+' JSON',
172
+ role: 'channel for json data'
173
+ },
174
+ native: {},
175
+ });
176
+
177
+ await ctx.setObjectNotExistsAsync(this.config.stateBase+'.tree', {
178
+ type: 'channel',
179
+ common: {
180
+ name: this.config.stateBase+' TREE',
181
+ role: 'channel for tree data'
182
+ },
183
+ native: {},
184
+ });
185
+
186
+ await ctx.setObjectNotExistsAsync(this.config.stateBase+'.raw', {
187
+ type: 'channel',
188
+ common: {
189
+ name: this.config.stateBase+' RAW',
190
+ role: 'channel for raw data'
191
+ },
192
+ native: {},
193
+ });
194
+ }
195
+ await this.storageDids.readKnownDids(ctx, opMode);
196
+ }
197
+
198
+ async setOpMode(mode) {
199
+ if (this.opModes.includes(mode)) {
200
+ this.opMode = mode;
201
+ }
202
+ }
203
+
204
+ async getOpMode() {
205
+ return(this.opMode);
206
+ }
207
+
208
+ toHex(d) {
209
+ // Convert integer to hex string of length len
210
+ return ('00'+(Number(d).toString(16))).slice(-2);
211
+ }
212
+
213
+ arr2Hex(arr) {
214
+ // Convert byte array to hex string
215
+ let hs = '';
216
+ for (const v in arr) { hs += this.toHex(arr[v]); }
217
+ return hs;
218
+ }
219
+
220
+ toByteArray(hs) {
221
+ // Convert hex string, e.g. '21A8' to byte array: [33,168]
222
+ const ba = [];
223
+ for (let i=0; i<hs.length/2; i++) {
224
+ ba.push(parseInt(hs.slice(2*i,2*i+2), 16));
225
+ }
226
+ return ba;
227
+ }
228
+
229
+ async storeStatistics(ctx, ctxWorker, forceStore) {
230
+ if (['standby','normal','udsDidScan'].includes(await this.getOpMode())) {
231
+ const ts = new Date().getTime();
232
+ if ( (!forceStore) && (ts < ctxWorker.stat.nextTs) ) return; // Min. time step not reached. Do not store.
233
+ if (ctxWorker.stat) await ctx.setStateAsync(ctxWorker.config.stateBase+'.info.'+this.config.statId, JSON.stringify(ctxWorker.stat), true);
234
+ ctxWorker.stat.nextTs = ts+ctxWorker.stat.tsMinStep;
235
+ }
236
+ }
237
+
238
+ async encodeDataCAN(ctx, ctxWorker, did, data) {
239
+ // Encode data for given did
240
+ let val;
241
+ if (did in this.storageDids.dids) {
242
+ const cdi = this.storageDids.dids[did]; // Infos about did codec
243
+ try {
244
+ const codec = await new E3.O3Ecodecs[cdi.codec](cdi.len, cdi.id, cdi.args);
245
+ val = await codec.encode(data);
246
+ } catch(e) {
247
+ await ctx.log.warn('encodeDataCAN(): Could not encode data for '+ctxWorker.config.stateBase+'.'+String(did)+'. err='+e.message);
248
+ val = null;
249
+ }
250
+ } else {
251
+ await ctx.log.warn('encodeDataCAN(): Did not found for '+ctxWorker.config.stateBase+'.'+String(did));
252
+ val = null;
253
+ }
254
+ return(val);
255
+ }
256
+
257
+ async decodeDataCAN(ctx, ctxWorker, did, data) {
258
+ async function storeObjectJson(ctx, stateId, obj) {
259
+ try {
260
+ await ctx.setObjectNotExistsAsync(stateId, {
261
+ type: 'state',
262
+ common: {
263
+ name: idStr,
264
+ type: 'string',
265
+ role: 'state',
266
+ read: true,
267
+ write: true,
268
+ },
269
+ native: {},
270
+ });
271
+ ctx.log.silly(String(stateId)+': '+JSON.stringify(obj));
272
+ await ctx.setStateAsync(stateId, JSON.stringify(obj), true);
273
+ }
274
+ catch(e) {
275
+ ctx.log.error('Storage of did '+stateId+'.'+String(did)+' failed. err='+e.message);
276
+ }
277
+ }
278
+ async function storeObjectTree(ctx, stateId, obj) {
279
+ try {
280
+ if (typeof(obj) == 'object') {
281
+ if (Object.keys(obj).length <= 100) {
282
+ for (const [key, itm] of Object.entries(obj)) {
283
+ await storeObjectTree(ctx, String(stateId)+'.'+String(key),itm);
284
+ }
285
+ } else {
286
+ ctx.log.error('Did valuation aborted. Too many members ('+String(Object.keys(obj).length)+') '+stateId+'.'+String(did));
287
+ }
288
+ } else {
289
+ await storeObjectJson(ctx, stateId, obj);
290
+ }
291
+ }
292
+ catch(e) {
293
+ ctx.log.error('Storage of did '+stateId+'.'+String(did)+' failed. err='+e.message);
294
+ }
295
+ }
296
+
297
+ if (this.opMode == this.opModes[0]) { return; }
298
+ const raw = this.arr2Hex(data);
299
+ let idStr, val, common, cdi, codec;
300
+ if (did in this.storageDids.dids) {
301
+ cdi = this.storageDids.dids[did]; // Infos about did codec
302
+ if (cdi.len == data.length) {
303
+ try {
304
+ codec = await new E3.O3Ecodecs[cdi.codec](cdi.len, cdi.id, cdi.args);
305
+ } catch(e) {
306
+ ctx.log.warn('Could not retreive codec for '+ctxWorker.config.stateBase+'.'+String(did)+'. err='+e.message);
307
+ codec = 'RawCodec';
308
+ }
309
+ idStr = cdi.id;
310
+ common = true;
311
+ try {
312
+ val = await codec.decode(data);
313
+ }
314
+ catch(e) {
315
+ val = raw;
316
+ ctx.log.warn('Codec failed: '+ctxWorker.canIDhex+'.'+String(did)+'. err='+e.message);
317
+ }
318
+ } else {
319
+ idStr = 'DeviceSpecific';
320
+ val = raw;
321
+ common = false;
322
+ }
323
+ } else {
324
+ idStr = 'DeviceSpecific';
325
+ val = raw;
326
+ common = false;
327
+ }
328
+ if (val != null) {
329
+ const didStr = '000'+String(did);
330
+ const stateIdJson = this.config.stateBase+'.json.'+didStr.slice(-4)+'_'+idStr;
331
+ const stateIdTree = this.config.stateBase+'.tree.'+didStr.slice(-4)+'_'+idStr;
332
+ const stateIdRaw = this.config.stateBase+'.raw.'+didStr.slice(-4)+'_'+idStr;
333
+ let didInfo;
334
+ switch (this.opMode) {
335
+ case this.opModes[0]: // 'standby'
336
+ break;
337
+ case this.opModes[1]: // 'udsDevScan'
338
+ this.udsScanResult = {
339
+ did: did,
340
+ didInfo: {
341
+ id : idStr,
342
+ len: data.length
343
+ },
344
+ val: val,
345
+ common: common
346
+ };
347
+ if (ctxWorker.callback) await ctxWorker.callback(ctx, ctxWorker, ['ok', this.udsScanResult]);
348
+ break;
349
+ case this.opModes[2]: // 'udsDidScan'
350
+ if (common) {
351
+ didInfo = {
352
+ id : idStr,
353
+ len: cdi.len,
354
+ codec: cdi.codec,
355
+ args: cdi.args
356
+ };
357
+ } else {
358
+ didInfo = {
359
+ id : idStr,
360
+ len: data.length,
361
+ codec: 'RawCodec',
362
+ args : {}
363
+ };
364
+ }
365
+ this.udsScanResult = {
366
+ did: did,
367
+ didInfo: didInfo,
368
+ val: val,
369
+ common: common
370
+ };
371
+ if (ctxWorker.callback) await ctxWorker.callback(ctx, ctxWorker, ['ok', this.udsScanResult]);
372
+ await storeObjectTree(ctx, stateIdTree, val);
373
+ await storeObjectJson(ctx, stateIdJson, val);
374
+ await storeObjectJson(ctx, stateIdRaw, raw);
375
+ await this.storeStatistics(ctx, ctxWorker, false);
376
+ break;
377
+ case this.opModes[3]: // 'normal'
378
+ await storeObjectTree(ctx, stateIdTree, val);
379
+ await storeObjectJson(ctx, stateIdJson, val);
380
+ await storeObjectJson(ctx, stateIdRaw, raw);
381
+ await this.storeStatistics(ctx, ctxWorker, false);
382
+ break;
383
+ case this.opModes[4]: // 'TEST'
384
+ return val;
385
+ default:
386
+ ctx.log.error('Invalid opMode at class storage. Change to "standby"');
387
+ this.opMode = this.opModes[0];
388
+ }
389
+ }
390
+ }
391
+ }
392
+
393
+ module.exports = {
394
+ storageDids,
395
+ storage
396
+ };