iobroker.e3oncan 0.6.13 → 0.6.15

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/README.md CHANGED
@@ -15,18 +15,22 @@
15
15
  # Basic concept
16
16
  Viessmann E3 series devices (One Base) are doing a lot of data exchange on CAN bus.
17
17
 
18
- This adapter can listen to this communication and extract many useful information. The often used energy meter E380 CA is also supported.
18
+ This adapter can listen to this communication and extract many useful information. The often used energy meter E380 CA also is supported.
19
19
 
20
- In parallel UDSonCAN service ReadByDid is supported. Informations not available via listening can be actively requested. This protocol is also used by other equipment, e.g. by well known WAGO gateway.
20
+ In parallel **reading of datapoints** (ReadByDid) is supported. Informations not available via listening can be actively requested. The UDSonCAN protocol is also used by other equipment, e.g. by well known WAGO gateway.
21
21
 
22
- Important parts are based on the project [open3e](https://github.com/open3e).
22
+ **Writing of datapoints** via UDSonCAN (WriteByDid) is supported as well. By writing to datapoints it's possible to change setpoints, schedules and so on. It's even possible to add new schedules e.g. for domestic hot water circulation pump.
23
23
 
24
- A python based implementation of a pure listening approach using MQTT messaging is also availabe, see [E3onCAN](https://github.com/MyHomeMyData/E3onCAN).
24
+ Writing of data is triggered by storing the corresponding state with `Acknowledged` not checked (ack=false) - yes, it's that simple! The datapoint will be read again from device and stored in the state two seconds after writing. If state not get's acknowledged, please take a look to the logs.
25
25
 
26
- **Present implementation supports reading and writing of datapoints via UDSonCAN (ReadByDid and WriteByDid).** By writing to datapoints it's possible to change setpoints, schedules and so on. It's even possible to add new schedules e.g. for domestic hot water circulation pump. Writing is restricted to a set of datapoints using a white list.
26
+ Writing is restricted to a set of datapoints using a **white list**. The list is stored in the info section of each device, e.g. at `e3oncan.0.vitocal.info.udsDidsWritable`. You can add more datapoints by editing this state. Make sure, **not** to check `Acknowledged` when saving the state.
27
27
 
28
28
  During first start of adapter instance a device scan will be done providing a list of all available devices for configuration dialog.
29
- A scan for datapoints of each device is also available and should be done during first setup.
29
+ A scan for datapoints of each device should be done during first setup.
30
+
31
+ Important parts of this adpater are based on the project [open3e](https://github.com/open3e).
32
+
33
+ A python based implementation of a pure listening approach using MQTT messaging is also availabe, see [E3onCAN](https://github.com/MyHomeMyData/E3onCAN).
30
34
 
31
35
  # Getting started
32
36
 
@@ -85,8 +89,8 @@ You may use datapoints informations on tab "LIST OF DATAPOINTS" for reference (o
85
89
 
86
90
  ## What is different to open3e project?
87
91
  * Obviously, the main differece is the direct integration to ioBroker. Configuration can be done via dialogs, data get's directly listed in object trees.
88
- * **WriteByDid is supported for all data types** but restricted to datapoints on device specific white list, see info section of devices. You need to do a datapoint scan to enable writing. Writing of data is triggered by storing the datapoint with ack=false - yes, it's that simple! The datapoint will be read again from device two seconds after writing. If datapoint not get's acknowledged, please take a look to the logs.
89
92
  * In addition to open3e real time collecting of data via listening is supported.
93
+ * Writing of data is much simpler. Just change the data in corresponding state and press Save button.
90
94
 
91
95
  ## May open3e be used in parallel?
92
96
  Yes, that is possible under certain conditions:
@@ -98,6 +102,15 @@ Yes, that is possible under certain conditions:
98
102
  Placeholder for the next version (at the beginning of the line):
99
103
  ### **WORK IN PROGRESS**
100
104
  -->
105
+ ### 0.6.15 (2024-01-23)
106
+ * (MyHomeMyData) Fix for Utf8 codec for handling of special characters, e.g. umlauts
107
+
108
+ ### 0.6.14 (2024-01-22)
109
+ * (MyHomeMyData) Replace '.' by '_' in datapoint ids to avoid unwanted sub structure in data states
110
+ * (MyHomeMyData) Added more informations about white list for writables in Readme.
111
+ * (MyHomeMyData) Recognize loss of CAN connection.
112
+ * (MyHomeMyData) Improved handling of info.connection.
113
+
101
114
  ### 0.6.13 (2024-01-20)
102
115
  * (MyHomeMyData) Now supports multiple definitions of same schedule on a device
103
116
  * (MyHomeMyData) Added unit test cases for codecs
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "e3oncan",
4
- "version": "0.6.13",
4
+ "version": "0.6.15",
5
5
  "news": {
6
+ "0.6.15": {
7
+ "en": "Fix for Utf8 codec for handling of special characters, e.g. umlauts",
8
+ "de": "Fix für Utf8-Codec zum Umgang mit Sonderzeichen, z.B. umlauts",
9
+ "ru": "Fix for Utf8 codec для обработки специальных символов, например, umlauts",
10
+ "pt": "Fix para Codec Utf8 para manipulação de caracteres especiais, por exemplo, umlauts",
11
+ "nl": "Fix voor Utf8 codec voor het hanteren van speciale tekens, bijvoorbeeld umlauts",
12
+ "fr": "Correction pour Codec Utf8 pour la manipulation de caractères spéciaux, p.ex. umlauts",
13
+ "it": "Fissaggio per Codice Utf8 per la gestione di caratteri speciali, ad esempio umlauts",
14
+ "es": "Corrección Utf8 codec para el manejo de caracteres especiales, por ejemplo umlauts",
15
+ "pl": "Napraw dla Kod Utf8 do obsługi znaków specjalnych, np. umlautów",
16
+ "uk": "Фіксація Utf8 кодека для обробки спеціальних символів, наприклад, umlauts",
17
+ "zh-cn": "修复 Utf8 处理特殊字符的编码器, 例如 umlauts"
18
+ },
19
+ "0.6.14": {
20
+ "en": "Replace '.' by '_' in datapoint ids to avoid unwanted sub structure in data states\nAdded more informations about white list for writables in Readme.\nRecognize loss of CAN connection.\nImproved handling of info.connection.",
21
+ "de": "Ersetzen Sie '.' durch '_' in Datapoint ids, um unerwünschte Unterstruktur in Datenzuständen zu vermeiden\nWeitere Informationen zur Weißen Liste für schreibbare in Readme.\nErkennen Verlust der CAN-Verbindung.\nVerbesserte Handhabung von info.connection.",
22
+ "ru": "Заменить '.' на '_' в идентификаторах точки данных, чтобы избежать нежелательной субструктуры в состояниях данных\nДобавлено больше информации о белом списке для редакций в Readme.\nУзнать потерю соединения CAN.\nУлучшена обработка info.connection.",
23
+ "pt": "Substituir '.' por '_' em datapoint ids para evitar subestrutura indesejada em estados de dados\nAdicionado mais informações sobre lista branca para writables em Readme.\nReconhecer a perda de conexão CAN.\nMelhor manuseio de info.connection.",
24
+ "nl": "Vervang '.' door '_' in datapoint ids om ongewenste substructuur in data states te voorkomen\nToegevoegd meer informatie over witte lijst voor schrijfsels in Readme.\nHerkennen verlies van KAN verbinding.\nVerbeterde behandeling van info.connectie.",
25
+ "fr": "Remplacer '.' par '_' dans les identifiants de points de données pour éviter une sous-structure indésirable dans les états de données\nAjout de plus d'informations sur la liste blanche pour les écritures dans Readme.\nReconnaître la perte de connexion CAN.\nAmélioration de la gestion d'info.connection.",
26
+ "it": "Sostituire '.' da '_' in datapoint ids per evitare la struttura sub indesiderata in stati di dati\nAggiunto più informazioni su white list per writables in Readme.\nRiconoscere la perdita di connessione CAN.\nGestione migliorata di info.connection.",
27
+ "es": "Reemplazar '.' por '_' en datospoint ids para evitar la subestructura no deseada en los estados de datos\nAgregó más información sobre la lista blanca para writables en Readme.\nReconocer la pérdida de conexión CAN.\nMejor manejo de info.conexión.",
28
+ "pl": "Zastąp '.' przez' _ 'w idach punktów danych, aby uniknąć niepożądanej podstruktury w stanach danych\nDodano więcej informacji na temat białej listy pism w Readme.\nRozpoznaj utratę połączenia CAN.\nUlepszone obchodzenie się z info.connection.",
29
+ "uk": "Замініть \".\" по \"_\" у полицях даних, щоб уникнути небажаної структури під даних\nДодайте більше інформації про білий список для життєздатних в Readme.\nВизначте втрату з'єднання CAN.\nПокращення обробки інформації.",
30
+ "zh-cn": "在数据点编号中将“.”改为“_”,以避免数据状态中不必要的子结构\n在Readme中添加了更多关于白名单的可写信息.\n识别失去的CAN连接.\n完善信息联通处理."
31
+ },
6
32
  "0.6.13": {
7
33
  "en": "Now supports multiple definitions of same schedule on a device \nAdded unit test cases for codecs",
8
34
  "de": "Jetzt unterstützt mehrere Definitionen desselben Zeitplans auf einem Gerät\nHinzugefügte Einheit Testfälle für Codecs",
@@ -67,32 +93,6 @@
67
93
  "pl": "Bugfix: Tylko Linux jest obsługiwany",
68
94
  "uk": "Помилки: Тільки Linux підтримується",
69
95
  "zh-cn": "错误修正 : 仅支持 Linux"
70
- },
71
- "0.6.8": {
72
- "en": "Initial npm version",
73
- "de": "Ursprüngliche npm-Version",
74
- "ru": "Первоначальная версия npm",
75
- "pt": "Versão inicial npm",
76
- "nl": "Eerste npm-versie",
77
- "fr": "Version initiale npm",
78
- "it": "Versione npm iniziale",
79
- "es": "Versión inicial npm",
80
- "pl": "Początkowa wersja npm",
81
- "uk": "Початкова версія npm",
82
- "zh-cn": "初始 npm 版本"
83
- },
84
- "0.6.6": {
85
- "en": "initial npm release",
86
- "de": "Erstveröffentlichung auf npm",
87
- "ru": "Начальная версия",
88
- "pt": "lançamento inicial",
89
- "nl": "Eerste uitgave",
90
- "fr": "Première version",
91
- "it": "Versione iniziale",
92
- "es": "Versión inicial",
93
- "pl": "Pierwsze wydanie",
94
- "uk": "Початкова версія",
95
- "zh-cn": "首次出版"
96
96
  }
97
97
  },
98
98
  "title": "Viessmann E3 CAN do",
package/lib/canUds.js CHANGED
@@ -365,17 +365,21 @@ class uds {
365
365
  switch (dcs.channel) {
366
366
  case 'json':
367
367
  // Change in json data
368
- byteArr = await this.storage.encodeDataCAN(ctx, this, did, JSON.parse(state.val));
369
- if (byteArr) {
370
- await this.pushCmnd(ctx, 'write', [[did,byteArr]]);
371
- setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms
372
- } else {
373
- ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.');
368
+ try {
369
+ byteArr = await this.storage.encodeDataCAN(ctx, this, did, await JSON.parse(state.val));
370
+ if (byteArr) {
371
+ await this.pushCmnd(ctx, 'write', [[did,byteArr]]);
372
+ setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms
373
+ } else {
374
+ ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.');
375
+ }
376
+ } catch(e) {
377
+ ctx.log.error('WriteByDid(): Encoding of data failed on '+this.config.stateBase+'.'+String(did)+'; err='+JSON.stringify(e));
374
378
  }
375
379
  break;
376
380
  case 'raw':
377
381
  // Change in raw data
378
- byteArr = this.storage.toByteArray(JSON.parse(state.val));
382
+ byteArr = this.storage.toByteArray(await JSON.parse(state.val));
379
383
  if (byteArr) {
380
384
  await this.pushCmnd(ctx, 'write', [[did,byteArr]]);
381
385
  setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms
@@ -389,12 +393,16 @@ class uds {
389
393
  lenBaseId = ctx.namespace.length+dcs.device.length+dcs.channel.length+dcs.state.length+3;
390
394
  if (id.length == lenBaseId) {
391
395
  // Scalar value w/o sub structure
392
- byteArr = await this.storage.encodeDataCAN(ctx, this, did, JSON.parse(state.val));
393
- if (byteArr) {
394
- await this.pushCmnd(ctx, 'write', [[did,byteArr]]);
395
- setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms
396
- } else {
397
- ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.');
396
+ try {
397
+ byteArr = await this.storage.encodeDataCAN(ctx, this, did, await JSON.parse(state.val));
398
+ if (byteArr) {
399
+ await this.pushCmnd(ctx, 'write', [[did,byteArr]]);
400
+ setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,this,did); // Read value after 2500 ms
401
+ } else {
402
+ ctx.log.error('User command UDS WriteByDid on '+this.config.stateBase+': Encoding of data failed.');
403
+ }
404
+ } catch(e) {
405
+ ctx.log.error('WriteByDid(): Encoding of data failed on '+this.config.stateBase+'.'+String(did)+'; err='+JSON.stringify(e));
398
406
  }
399
407
  break;
400
408
  }
@@ -402,21 +410,6 @@ class uds {
402
410
  // Build json object for complete object tree of changed did:
403
411
  await ctx.getStatesOf(dcs.device,dcs.device+'.'+dcs.channel,async function(err,obj) {
404
412
  // Get all states for changed device.channel
405
- /*
406
- // Probalby better solution based on numbers as marker for arrays. But not working yet.
407
- function isNumber(value) { return /^\d+$/.test(value); }
408
- function insertDictSubVal(dict, keyArr, isArray, val) {
409
- if (keyArr.length == 1) {
410
- const key = keyArr[0];
411
- if (isArray) dict[key].push(val); else dict[key] = val;
412
- } else {
413
- const key = keyArr.shift();
414
- const nextIsArray = (((keyArr.length) && (isNumber(keyArr[0]))) ? true : false);
415
- if (!(key in dict)) if (nextIsArray) dict[key] = []; else dict[key] = {};
416
- insertDictSubVal(dict[key],keyArr, nextIsArray, val);
417
- }
418
- }
419
- */
420
413
  function insertDictSubVal(dict, keyArr, val) {
421
414
  const listLabels = ['ListEntries','Schedules','TopologyElement'];
422
415
  if (keyArr.length == 1) {
@@ -437,12 +430,16 @@ class uds {
437
430
  insertDictSubVal(treeDict, label.split('.'), val);
438
431
  }
439
432
  }
440
- byteArr = await ctxWorker.storage.encodeDataCAN(ctx, ctxWorker, did, treeDict);
441
- if (byteArr) {
442
- await ctxWorker.pushCmnd(ctx, 'write', [[did,byteArr]]);
443
- setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,ctxWorker,did); // Read value after 2500 ms
444
- } else {
445
- ctx.log.error('User command UDS WriteByDid on '+ctxWorker.config.stateBase+': Encoding of data failed.');
433
+ try {
434
+ byteArr = await ctxWorker.storage.encodeDataCAN(ctx, ctxWorker, did, treeDict);
435
+ if (byteArr) {
436
+ await ctxWorker.pushCmnd(ctx, 'write', [[did,byteArr]]);
437
+ setTimeout(function(ctxWorker,did){ctxWorker.cmndsQueue.push({'mode':'read', 'did': did});},2500,ctxWorker,did); // Read value after 2500 ms
438
+ } else {
439
+ ctx.log.error('User command UDS WriteByDid on '+ctxWorker.config.stateBase+': Encoding of data failed.');
440
+ }
441
+ } catch(e) {
442
+ ctx.log.error('WriteByDid(): Encoding of data failed on '+ctxWorker.config.stateBase+'.'+String(did)+'; err='+JSON.stringify(e));
446
443
  }
447
444
  });
448
445
  break;
package/lib/codecs.js CHANGED
@@ -205,18 +205,28 @@ class O3EUtf8 {
205
205
  this.id = idStr;
206
206
  this.offset = _args.offset;
207
207
  }
208
+
208
209
  encode(data) {
209
- const encoder = new TextEncoder();
210
- const result = Object.values(encoder.encode(data));
211
- if (result.length > this.string_len) throw new Error('O3EUtf8.encode() result too long: '+this.id+' - '+String(result.length)+' > '+String(this.string_len));
210
+ let result;
211
+ try {
212
+ result = Object.values(new TextEncoder().encode(data));
213
+ } catch(e) {
214
+ throw new Error('O3EUtf8.encode(): Conversion from Utf8 failed '+this.id+'; err='+JSON.stringify(e));
215
+ }
216
+ if (result.length > this.string_len) throw new Error('O3EUtf8.encode(): Result too long: '+this.id+' - '+String(result.length)+' > '+String(this.string_len));
212
217
  return result.concat(Array.from(Array(this.string_len-result.length), () => 0));
213
218
 
214
219
  }
215
220
  decode(string_bin) {
216
- let mystr = '';
217
- for (let i=this.offset; i<this.offset+this.string_len;i++) { mystr += String.fromCharCode(string_bin[i]); }
218
- // eslint-disable-next-line no-control-regex
219
- return mystr.replace(/[\u0000-\u0008,\u000A-\u001F,\u007F-\u00A0]+/g, '');
221
+ let i = string_bin.slice(this.offset).indexOf(0);
222
+ if (i == -1) { i = string_bin.length-this.offset; }
223
+ let result;
224
+ try {
225
+ result = new TextDecoder().decode(new Uint8Array(string_bin.slice(this.offset,i)));
226
+ } catch(e) {
227
+ throw new Error('O3EUtf8.decode(): Conversion to Utf8 failed '+this.id+'; err='+JSON.stringify(e));
228
+ }
229
+ return result;
220
230
  }
221
231
 
222
232
  __len__() {
@@ -38,6 +38,62 @@ function codecEncode(descr, expected, codec, len, id, args, val) {
38
38
  });
39
39
  }
40
40
 
41
+ // Utf8:
42
+ describe('codecs.js => O3EUtf8()', () => {
43
+ const raw_hex1 = '4865697a6bc3b6727065720000000000000000000000000000000000000000000000000000000000';
44
+ const raw1 = E3.toByteArray(raw_hex1);
45
+ const txt1 = 'Heizkörper';
46
+ it(`Decode ${JSON.stringify(txt1)}`, () => {
47
+ const f = new E3.O3Ecodecs.O3EUtf8(raw1.length,'test1Utf08', {offset:0});
48
+ const result = f.decode(raw1);
49
+ expect(JSON.stringify(result)).to.equal(JSON.stringify(txt1));
50
+ });
51
+ it(`Encode ${JSON.stringify(raw1)}`, () => {
52
+ const f = new E3.O3Ecodecs.O3EUtf8(raw1.length,'test2Utf08', {offset:0});
53
+ const result = f.encode(txt1);
54
+ expect(JSON.stringify(raw1)).to.equal(JSON.stringify(result));
55
+ });
56
+ const raw_hex2 = '4675c39f626f64656e6865697a756e67000000000000000000000000000000000000000000000000';
57
+ const raw2 = E3.toByteArray(raw_hex2);
58
+ const txt2 = 'Fußbodenheizung';
59
+ it(`Decode ${JSON.stringify(txt2)}`, () => {
60
+ const f = new E3.O3Ecodecs.O3EUtf8(raw2.length,'test3Utf08', {offset:0});
61
+ const result = f.decode(raw2);
62
+ expect(JSON.stringify(result)).to.equal(JSON.stringify(txt2));
63
+ });
64
+ it(`Encode ${JSON.stringify(raw2)}`, () => {
65
+ const f = new E3.O3Ecodecs.O3EUtf8(raw2.length,'test4Utf08', {offset:0});
66
+ const result = f.encode(txt2);
67
+ expect(JSON.stringify(raw2)).to.equal(JSON.stringify(result));
68
+ });
69
+ const raw_hex3 = '00000000000000000000000000000000000000000000000000000000000000000000000000000000';
70
+ const raw3 = E3.toByteArray(raw_hex3);
71
+ const txt3 = '';
72
+ it(`Decode ${JSON.stringify(txt3)}`, () => {
73
+ const f = new E3.O3Ecodecs.O3EUtf8(raw3.length,'test5Utf08', {offset:0});
74
+ const result = f.decode(raw3);
75
+ expect(JSON.stringify(result)).to.equal(JSON.stringify(txt3));
76
+ });
77
+ it(`Encode ${JSON.stringify(raw3)}`, () => {
78
+ const f = new E3.O3Ecodecs.O3EUtf8(raw3.length,'test5Utf08', {offset:0});
79
+ const result = f.encode(txt3);
80
+ expect(JSON.stringify(raw3)).to.equal(JSON.stringify(result));
81
+ });
82
+ const raw_hex4 = '4675c39f';
83
+ const raw4 = E3.toByteArray(raw_hex4);
84
+ const txt4 = 'Fuß';
85
+ it(`Decode ${JSON.stringify(txt4)}`, () => {
86
+ const f = new E3.O3Ecodecs.O3EUtf8(raw4.length,'test6Utf08', {offset:0});
87
+ const result = f.decode(raw4);
88
+ expect(JSON.stringify(result)).to.equal(JSON.stringify(txt4));
89
+ });
90
+ it(`Encode ${JSON.stringify(raw4)}`, () => {
91
+ const f = new E3.O3Ecodecs.O3EUtf8(raw4.length,'test6Utf08', {offset:0});
92
+ const result = f.encode(txt4);
93
+ expect(JSON.stringify(raw4)).to.equal(JSON.stringify(result));
94
+ });
95
+ });
96
+
41
97
  // val2byteArray:
42
98
  describe('codecs.js => val2byteArr()', () => {
43
99
  let val = -1;
@@ -13,6 +13,7 @@
13
13
  "434": "MixerSixCircuitRoomTemperatureSetpoint",
14
14
  "435": "VentilationStageTargetVolumeFlow",
15
15
  "436": "MixerSevenCircuitRoomTemperatureSetpoint",
16
+ "437": "BypassOperationState",
16
17
  "438": "MixerEightCircuitRoomTemperatureSetpoint",
17
18
  "451": "ExternalAlternatingCurrentPowerSetpoint",
18
19
  "491": "DomesticHotWaterCirculationPump",
@@ -23,6 +24,7 @@
23
24
  "525": "ExternalModulationSetpoint",
24
25
  "527": "FlowTemperatureTargetSetpoint",
25
26
  "528": "ExternalTargetFlowTemperatureSetpoint",
27
+ "531": "DomesticHotWaterOperationState",
26
28
  "533": "VentilationTargetOperationLevel",
27
29
  "537": "ExternalMixerOneCircuitTargetOperationMode",
28
30
  "538": "ExternalDomesticHotWaterTargetOperationMode",
@@ -101,6 +103,14 @@
101
103
  "1265": "FanTargetSpeedMeta",
102
104
  "1363": "FuelCellTargetOperationMode",
103
105
  "1394": "SolarChargingDomesticHotWaterSetpoint",
106
+ "1415": "MixerOneCircuitOperationState",
107
+ "1416": "MixerTwoCircuitOperationState",
108
+ "1417": "MixerThreeCircuitOperationState",
109
+ "1418": "MixerFourCircuitOperationState",
110
+ "1419": "MixerFiveCircuitOperationState",
111
+ "1420": "MixerSixCircuitOperationState",
112
+ "1421": "MixerSevenCircuitOperationState",
113
+ "1422": "MixerEightCircuitOperationState",
104
114
  "1439": "NoiseReductionTimeScheduleMonday",
105
115
  "1440": "NoiseReductionTimeScheduleTuesday",
106
116
  "1441": "NoiseReductionTimeScheduleWednesday",
@@ -109,6 +119,7 @@
109
119
  "1444": "NoiseReductionTimeScheduleSaturday",
110
120
  "1445": "NoiseReductionTimeScheduleSunday",
111
121
  "1505": "SolarStagnationTemperatureOffset",
122
+ "1552": "ElectricalEnergyStorageOperationState",
112
123
  "1555": "MixerOneCircuitFixedValueFlowTemperatureSetpoint",
113
124
  "1556": "MixerTwoCircuitFixedValueFlowTemperatureSetpoint",
114
125
  "1557": "MixerThreeCircuitFixedValueFlowTemperatureSetpoint",
@@ -121,6 +132,8 @@
121
132
  "1587": "ExternalAlternatingCurrentPowerSetpointMetaData",
122
133
  "1588": "AlternatingCurrentPowerSetpoint",
123
134
  "1589": "AlternatingCurrentPowerSetpointMetaData",
135
+ "1590": "ElectricalEnergySystemOperationState",
136
+ "1591": "ElectricalEnergyInverterOperationState",
124
137
  "1604": "GatewayExternalTargetFlowTemperatureSetpoint",
125
138
  "1605": "GatewayExternalHeatEngineTargetOperationMode",
126
139
  "1612": "ExternalMixerTwoCircuitTargetOperationMode",
@@ -373,6 +386,7 @@
373
386
  "2327": "VentilationTargetVolumeFlow",
374
387
  "2345": "CompressorSetpointPercent",
375
388
  "2353": "TargetDemandHeatProducer",
389
+ "2372": "VentilationControllerOperationState",
376
390
  "2373": "VentilationAirVolumeFlowBalancingOffset",
377
391
  "2417": "MixerOneCircuitTargetValueRelativeHumidityCooling",
378
392
  "2418": "MixerTwoCircuitTargetValueRelativeHumidityCooling",
@@ -400,6 +414,7 @@
400
414
  "2878": "SubcoolingSetpoint",
401
415
  "2883": "ExpansionValveMaximumOperatingPressureTemperatureSetpoint",
402
416
  "2985": "ExternalHeaterTemperatureSetpoint",
417
+ "2986": "ExternalHeaterOperationState",
403
418
  "3037": "ElectricityPriceTimeScheduleMonday",
404
419
  "3038": "ElectricityPriceTimeScheduleTuesday",
405
420
  "3039": "ElectricityPriceTimeScheduleWednesday",
package/lib/storage.js CHANGED
@@ -280,7 +280,8 @@ class storage {
280
280
  if (typeof(obj) == 'object') {
281
281
  if (Object.keys(obj).length <= 100) {
282
282
  for (const [key, itm] of Object.entries(obj)) {
283
- await storeObjectTree(ctx, String(stateId)+'.'+String(key),itm);
283
+ await storeObjectTree(ctx, String(stateId)+'.'+String(key).replace('.','_'),itm);
284
+ // No '.' in state id allowed to avoid sub structure
284
285
  }
285
286
  } else {
286
287
  ctx.log.error('Did valuation aborted. Too many members ('+String(Object.keys(obj).length)+') '+stateId+'.'+String(did));
@@ -306,7 +307,7 @@ class storage {
306
307
  ctx.log.warn('Could not retreive codec for '+ctxWorker.config.stateBase+'.'+String(did)+'. err='+e.message);
307
308
  codec = 'RawCodec';
308
309
  }
309
- idStr = cdi.id;
310
+ idStr = cdi.id.replace('.','_'); // No '.' in state allowed to avoid sub structure
310
311
  common = true;
311
312
  try {
312
313
  val = await codec.decode(data);
@@ -316,6 +317,7 @@ class storage {
316
317
  ctx.log.warn('Codec failed: '+ctxWorker.canIDhex+'.'+String(did)+'. err='+e.message);
317
318
  }
318
319
  } else {
320
+ // did length is diffetent from common did length => device specific did
319
321
  idStr = 'DeviceSpecific';
320
322
  val = raw;
321
323
  common = false;
package/main.js CHANGED
@@ -33,6 +33,7 @@ class E3oncan extends utils.Adapter {
33
33
  name: 'e3oncan',
34
34
  });
35
35
 
36
+ this.stoppingInstance = false; // true during unLoad()
36
37
  this.e380Collect = null; // E380 always is assigned to external bus
37
38
  this.E3CollectInt = {}; // Dict of collect devices on internal bus
38
39
  this.E3CollectExt = {}; // Dict of collect devices on external bus
@@ -43,6 +44,8 @@ class E3oncan extends utils.Adapter {
43
44
  this.channelExtName = '';
44
45
  this.channelInt = null;
45
46
  this.channelIntName = '';
47
+ this.cntCanConnDesired = 0; // Number of activated CAN connections in config
48
+ this.cntCanConnActual = 0; // Number if actualy connected CAN buses
46
49
 
47
50
  this.udsWorkers = {};
48
51
  this.udsTimeout = 7500; // Timeout (ms) for normal UDS communication
@@ -89,8 +92,9 @@ class E3oncan extends utils.Adapter {
89
92
 
90
93
  // @ts-ignore
91
94
  if (this.config.canExtActivated) {
95
+ this.cntCanConnDesired++;
92
96
  // @ts-ignore
93
- [this.channelExt, this.channelExtName] = await this.connectToCan(this.channelExt, this.config.canExtName, this.onCanMsgExt);
97
+ [this.channelExt, this.channelExtName] = await this.connectToCan(this.channelExt, this.config.canExtName, this.onCanMsgExt, this.onCanExtStopped);
94
98
  }
95
99
 
96
100
  // Setup internal CAN bus if required
@@ -98,8 +102,14 @@ class E3oncan extends utils.Adapter {
98
102
 
99
103
  // @ts-ignore
100
104
  if (this.config.canIntActivated) {
105
+ this.cntCanConnDesired++;
101
106
  // @ts-ignore
102
- [this.channelInt, this.channelIntName] = await this.connectToCan(this.channelInt, this.config.canIntName, this.onCanMsgInt);
107
+ [this.channelInt, this.channelIntName] = await this.connectToCan(this.channelInt, this.config.canIntName, this.onCanMsgInt, this.onCanIntStopped);
108
+ }
109
+
110
+ if (this.cntCanConnActual == this.cntCanConnDesired) {
111
+ // All configured CAN connections are established
112
+ await this.setState('info.connection', true, true);
103
113
  }
104
114
 
105
115
  // Setup E380 collect worker:
@@ -119,14 +129,15 @@ class E3oncan extends utils.Adapter {
119
129
 
120
130
  // Setup CAN busses
121
131
 
122
- async connectToCan(channel, name, onMsg) {
132
+ async connectToCan(channel, name, onMsg, onStop) {
123
133
  let chName = name;
124
134
  if (!channel) {
125
135
  try {
126
136
  channel = can.createRawChannel(name, true);
127
137
  await channel.addListener('onMessage', onMsg, this);
138
+ await channel.addListener('onStopped', onStop, this);
128
139
  await channel.start();
129
- await this.setState('info.connection', true, true);
140
+ this.cntCanConnActual++;
130
141
  await this.log.info('CAN-Adapter connected: '+name);
131
142
  } catch (e) {
132
143
  await this.log.error(`Could not connect to CAN-Adapter "${name}" - err=${e.message}`);
@@ -148,7 +159,6 @@ class E3oncan extends utils.Adapter {
148
159
  channel = null;
149
160
  }
150
161
  }
151
- return([channel,'']);
152
162
  }
153
163
 
154
164
  // Setup E380 collect worker:
@@ -247,6 +257,7 @@ class E3oncan extends utils.Adapter {
247
257
  */
248
258
  async onUnload(callback) {
249
259
  try {
260
+ this.stoppingInstance = true;
250
261
  // Stop UDS workers:
251
262
  for (const worker of Object.values(this.E3UdsWorkers)) await worker.stop(this);
252
263
  for (const worker of Object.values(this.udsScanWorker.workers)) await worker.stop(this);
@@ -261,6 +272,7 @@ class E3oncan extends utils.Adapter {
261
272
  await this.disconnectFromCan(this.channelExt,this.config.canExtName);
262
273
  // @ts-ignore
263
274
  await this.disconnectFromCan(this.channelInt,this.config.canIntName);
275
+ this.setState('info.connection', false, true);
264
276
 
265
277
  callback();
266
278
  } catch (e) {
@@ -455,6 +467,24 @@ class E3oncan extends utils.Adapter {
455
467
  }
456
468
  }
457
469
 
470
+ onCanExtStopped() {
471
+ if (!this.stoppingInstance) {
472
+ // External CAN connection was terminated unexpectedly
473
+ this.log.error('External CAN bus was stopped.');
474
+ }
475
+ this.cntCanConnActual--;
476
+ this.setState('info.connection', false, true);
477
+ }
478
+
479
+ onCanIntStopped() {
480
+ if (!this.stoppingInstance) {
481
+ // External CAN connection was terminated unexpectedly
482
+ this.log.error('Internal CAN bus was stopped.');
483
+ }
484
+ this.cntCanConnActual--;
485
+ this.setState('info.connection', false, true);
486
+ }
487
+
458
488
  onCanMsgExt(msg) {
459
489
  if ( (this.e380Collect) && (this.e380Collect.config.canID.includes(msg.id)) ) { this.e380Collect.msgCollect(this, msg); }
460
490
  if (this.E3CollectExt[msg.id]) this.E3CollectExt[msg.id].msgCollect(this, msg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.e3oncan",
3
- "version": "0.6.13",
3
+ "version": "0.6.15",
4
4
  "description": "Collect data on CAN bus for Viessmann E3 devices, e.g. Vitocal, Vitocharge, Energy Meter E380",
5
5
  "author": {
6
6
  "name": "Juergen",