iobroker.e3oncan 0.6.11 → 0.6.14
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 +29 -9
- package/io-package.json +40 -14
- package/lib/canUds.js +16 -11
- package/lib/codecs.js +16 -8
- package/lib/codecs.test.js +222 -0
- package/lib/didsE3Writables.json +33 -0
- package/lib/storage.js +4 -2
- package/main.js +44 -9
- package/package.json +2 -2
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
|
@@ -51,7 +55,7 @@ This step is not mandatory but strongly recomended. If you would like to write t
|
|
|
51
55
|
* Last step is to configure schedules for collecting data on tab "ASSIGNMENTS TO EXTERNAL CAN ADAPTER".
|
|
52
56
|
* For **energy meter E380** (if available in your setup) you just can activate or not. Please notice the value "Min. update time (s)". Updates to single datapoints are done no faster than the given value (default is 5 seconds). By choosing zero every received data will be stored. Since E380 is sending data very fast (more than 20 values per second), it's recommended not to use zero here. This would put a high load on the ioBroker system.
|
|
53
57
|
* If you have connected E3 devices via CAN bus, e.g. Vitocal and VX3, you can collect data exchanged between those devices in realtime by listening. Press "+" to add a line, check "active" chackbox, select a device and edit "Min. update time (s)". It's feasable to use 0s here, however, I recommend to keep to the 5s.
|
|
54
|
-
* Finally, you may add schedules for requesting data via UDSonCAN protocol. Again press "+" button and edit the
|
|
58
|
+
* Finally, you may add schedules for requesting data via UDSonCAN protocol. Again press "+" button and edit the settings. You may have several schedules on each device. By this you can request some datapoints more often than others. Default value of 0 for "Schedule (s)" means, those datapoints will be requested just once during startup of the instance.
|
|
55
59
|
You may use datapoints informations on tab "LIST OF DATAPOINTS" for reference (opening on 2nd tab could help).
|
|
56
60
|
* If you have configured a CAN adapter connected to the **internal CAN bus**, a tab "ASSIGNMENTS TO INTERNAL CAN ADAPTER" is visible. Please configure the devices for colletion there. UDSonCAN is not supported on internal CAN bus by E3 devices.
|
|
57
61
|
* That's it. Press "SAVE & CLOSE" button and check the data collected in object tree.
|
|
@@ -81,12 +85,12 @@ You may use datapoints informations on tab "LIST OF DATAPOINTS" for reference (o
|
|
|
81
85
|
* Therfore from my point of view, combination of both methods is best approach.
|
|
82
86
|
|
|
83
87
|
## Limitation of collecting data
|
|
84
|
-
* At present, the communication protocol is known only for Vitocal (listener on CAN id 0x693)
|
|
88
|
+
* At present, the communication protocol is known only for Vitocal (listener on CAN id 0x693), Vitocharge VX3 and Vitoair (both listener on CAN id 0x451).
|
|
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,22 @@ 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.14 (2024-01-22)
|
|
106
|
+
* (MyHomeMyData) Replace '.' by '_' in datapoint ids to avoid unwanted sub structure in data states
|
|
107
|
+
* (MyHomeMyData) Added more informations about white list for writables in Readme.
|
|
108
|
+
* (MyHomeMyData) Recognize loss of CAN connection.
|
|
109
|
+
* (MyHomeMyData) Improved handling of info.connection.
|
|
110
|
+
|
|
111
|
+
### 0.6.13 (2024-01-20)
|
|
112
|
+
* (MyHomeMyData) Now supports multiple definitions of same schedule on a device
|
|
113
|
+
* (MyHomeMyData) Added unit test cases for codecs
|
|
114
|
+
|
|
115
|
+
### 0.6.12 (2024-01-19)
|
|
116
|
+
* (MyHomeMyData) Added datapoints to list writable dids
|
|
117
|
+
* (MyHomeMyData) Added unit test cases for codecs
|
|
118
|
+
* (MyHomeMyData) Improved speed of codes for numerical values
|
|
119
|
+
* (MyHomeMyData) Improved error messages on UDS negative response
|
|
120
|
+
|
|
101
121
|
### 0.6.11 (2024-01-17)
|
|
102
122
|
* (MyHomeMyData) Improved layout of configuration dialog for device scan
|
|
103
123
|
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "e3oncan",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.14",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.6.14": {
|
|
7
|
+
"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.",
|
|
8
|
+
"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.",
|
|
9
|
+
"ru": "Заменить '.' на '_' в идентификаторах точки данных, чтобы избежать нежелательной субструктуры в состояниях данных\nДобавлено больше информации о белом списке для редакций в Readme.\nУзнать потерю соединения CAN.\nУлучшена обработка info.connection.",
|
|
10
|
+
"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.",
|
|
11
|
+
"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.",
|
|
12
|
+
"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.",
|
|
13
|
+
"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.",
|
|
14
|
+
"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.",
|
|
15
|
+
"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.",
|
|
16
|
+
"uk": "Замініть \".\" по \"_\" у полицях даних, щоб уникнути небажаної структури під даних\nДодайте більше інформації про білий список для життєздатних в Readme.\nВизначте втрату з'єднання CAN.\nПокращення обробки інформації.",
|
|
17
|
+
"zh-cn": "在数据点编号中将“.”改为“_”,以避免数据状态中不必要的子结构\n在Readme中添加了更多关于白名单的可写信息.\n识别失去的CAN连接.\n完善信息联通处理."
|
|
18
|
+
},
|
|
19
|
+
"0.6.13": {
|
|
20
|
+
"en": "Now supports multiple definitions of same schedule on a device \nAdded unit test cases for codecs",
|
|
21
|
+
"de": "Jetzt unterstützt mehrere Definitionen desselben Zeitplans auf einem Gerät\nHinzugefügte Einheit Testfälle für Codecs",
|
|
22
|
+
"ru": "Теперь поддерживает несколько определений одного и того же расписания на устройстве\n4.1.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.1.2.1.2.1.1.2.1.1.2.1.1.1.1.1.2.1.1.1.1.1.1.1.1.2.2.1.1.1.1.1.2.1.1.1.1.1.1.1.2.2.2.1.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.2.1.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1",
|
|
23
|
+
"pt": "Agora suporta várias definições da mesma programação em um dispositivo\nAdicionados casos de teste de unidade para codecs",
|
|
24
|
+
"nl": "Nu ondersteunt meerdere definities van hetzelfde schema op een apparaat\nToegevoegde testcases per eenheid voor codecs",
|
|
25
|
+
"fr": "Maintenant prend en charge plusieurs définitions de même horaire sur un appareil\nAjout de cas de test unitaire pour codecs",
|
|
26
|
+
"it": "Ora supporta più definizioni dello stesso programma su un dispositivo\nAggiunti casi di prova unità per codecs",
|
|
27
|
+
"es": "Ahora soporta múltiples definiciones del mismo horario en un dispositivo\nCasos de prueba de unidad adicionales para codecs",
|
|
28
|
+
"pl": "Teraz obsługuje wiele definicji tego samego harmonogramu na urządzeniu\nDodano jednostkowe przypadki badań kodeków",
|
|
29
|
+
"uk": "Тепер підтримує декілька визначень одного графіка на пристрої\nДодані випадки тестування блоку для кодеків",
|
|
30
|
+
"zh-cn": "现在支持设备上同一时刻表的多个定义\n为解码器添加单位测试例"
|
|
31
|
+
},
|
|
32
|
+
"0.6.12": {
|
|
33
|
+
"en": "Added datapoints to list writable dids\nAdded unit test cases for codecs\nImproved speed of codes for numerical values\nImproved error messages on UDS negative response",
|
|
34
|
+
"de": "Datenpunkte zur Liste der schreibbaren Dossiers hinzugefügt\nHinzugefügte Einheit Testfälle für Codecs\nVerbesserte Geschwindigkeit von Codes für Zahlenwerte\nVerbesserte Fehlermeldungen auf UDS negative Antwort",
|
|
35
|
+
"ru": "Добавлены точки данных в список значимых дел\n4.1.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.2.1.1.2.1.2.1.1.2.1.1.2.1.1.1.1.1.2.1.1.1.1.1.1.1.1.2.2.1.1.1.1.1.2.1.1.1.1.1.1.1.2.2.2.1.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.2.1.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1\nПовышение скорости кодов для численных значений\nУлучшенные сообщения об ошибках в негативном ответе на СОД",
|
|
36
|
+
"pt": "Adicionado datapoints para lista writable dids\nAdicionados casos de teste de unidade para codecs\nVelocidade melhorada de códigos para valores numéricos\nMensagens de erro melhoradas na resposta negativa do UDS",
|
|
37
|
+
"nl": "Toegevoegd datapunten aan lijst schrijfbare does\nToegevoegde testcases per eenheid voor codecs\nVerbeterde snelheid van codes voor numerieke waarden\nVerbeterde foutmeldingen op UDS-negatieve reactie",
|
|
38
|
+
"fr": "Ajout de points de données pour lister les dids en écriture\nAjout de cas de test unitaire pour codecs\nAmélioration de la vitesse des codes pour les valeurs numériques\nMessages d'erreur améliorés sur la réponse négative UDS",
|
|
39
|
+
"it": "Aggiunti i datapoints per elencare i fatti scrivibili\nAggiunti casi di prova unità per codecs\nVelocità migliorata dei codici per i valori numerici\nMessaggi di errore migliorati sulla risposta negativa UDS",
|
|
40
|
+
"es": "Puntos de datos añadidos a la lista de hechos\nCasos de prueba de unidad adicionales para codecs\nMejora de la velocidad de los códigos para los valores numéricos\nMensajes de error mejorados en respuesta negativa UDS",
|
|
41
|
+
"pl": "Dodano punkty danych do listy zapisanych didów\nDodano jednostkowe przypadki badań kodeków\nZwiększona prędkość kodów dla wartości numerycznych\nUlepszone komunikaty błędów w odpowiedzi negatywnej UDS",
|
|
42
|
+
"uk": "Додано точки даних для списання збитих даних\nДодані випадки тестування блоку для кодеків\nПокращена швидкість кодів для чисельних значень\nПокращуйте повідомлення про помилки у UDS",
|
|
43
|
+
"zh-cn": "添加数据点到列表可写作的\n为解码器添加单位测试例\n数字值代码速度提高\nUDS 负面响应上改进错误信息"
|
|
44
|
+
},
|
|
6
45
|
"0.6.11": {
|
|
7
46
|
"en": "Improved layout of configuration dialog for device scan",
|
|
8
47
|
"de": "Verbessertes Layout des Konfigurationsdialogs für den Gerätescan",
|
|
@@ -54,19 +93,6 @@
|
|
|
54
93
|
"pl": "Początkowa wersja npm",
|
|
55
94
|
"uk": "Початкова версія npm",
|
|
56
95
|
"zh-cn": "初始 npm 版本"
|
|
57
|
-
},
|
|
58
|
-
"0.6.6": {
|
|
59
|
-
"en": "initial npm release",
|
|
60
|
-
"de": "Erstveröffentlichung auf npm",
|
|
61
|
-
"ru": "Начальная версия",
|
|
62
|
-
"pt": "lançamento inicial",
|
|
63
|
-
"nl": "Eerste uitgave",
|
|
64
|
-
"fr": "Première version",
|
|
65
|
-
"it": "Versione iniziale",
|
|
66
|
-
"es": "Versión inicial",
|
|
67
|
-
"pl": "Pierwsze wydanie",
|
|
68
|
-
"uk": "Початкова версія",
|
|
69
|
-
"zh-cn": "首次出版"
|
|
70
96
|
}
|
|
71
97
|
},
|
|
72
98
|
"title": "Viessmann E3 CAN do",
|
package/lib/canUds.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const storage = require('./storage');
|
|
2
2
|
|
|
3
3
|
class scheduleLoop {
|
|
4
|
-
constructor(ctxGlobal, ctxLocal, schedule
|
|
4
|
+
constructor(ctxGlobal, ctxLocal, schedule) {
|
|
5
5
|
this.ctxGlobal = ctxGlobal;
|
|
6
6
|
this.ctxLocal = ctxLocal;
|
|
7
7
|
this.schedule = schedule;
|
|
8
|
-
this.dids =
|
|
8
|
+
this.dids = [];
|
|
9
9
|
this.schedHandle = null;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -30,6 +30,11 @@ class scheduleLoop {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
async addDids(dids) {
|
|
34
|
+
const didsArr = dids.replace(' ','').split(',');
|
|
35
|
+
this.dids = this.dids.concat(didsArr.map(function(str) { return parseInt(str); }));
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
async loop() {
|
|
34
39
|
await this.ctxGlobal.log.silly('UDS schedule: '+String(this.schedule)+' '+this.ctxLocal.canIDhex+'.'+JSON.stringify(this.dids));
|
|
35
40
|
await this.ctxLocal.pushCmnd(this.ctxGlobal, 'read', this.dids);
|
|
@@ -244,13 +249,13 @@ class uds {
|
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
async addSchedule(ctx, schedule, dids) {
|
|
247
|
-
if (!Object.keys(this.schedules).includes(schedule)) {
|
|
248
|
-
|
|
249
|
-
this.schedules[schedule] = new scheduleLoop(ctx, this, schedule
|
|
250
|
-
ctx.log.silly('UDS worker on '+this.config.stateBase+': Added schedule '+String(schedule)+'s
|
|
251
|
-
} else {
|
|
252
|
-
ctx.log.warn('UDS worker warning on '+this.config.stateBase+': Multiple definiton of schedule:'+String(schedule)+'s');
|
|
252
|
+
if (!Object.keys(this.schedules).includes(String(schedule))) {
|
|
253
|
+
// New schedule
|
|
254
|
+
this.schedules[schedule] = new scheduleLoop(ctx, this, schedule);
|
|
255
|
+
await ctx.log.silly('UDS worker on '+this.config.stateBase+': Added schedule '+String(schedule)+'s.');
|
|
253
256
|
}
|
|
257
|
+
await this.schedules[schedule].addDids(dids);
|
|
258
|
+
await ctx.log.silly('UDS worker on '+this.config.stateBase+': Added dids to schedule '+String(schedule)+'s '+JSON.stringify(this.schedules[schedule].dids));
|
|
254
259
|
}
|
|
255
260
|
|
|
256
261
|
async pushCmnd(ctx, mode, dids) {
|
|
@@ -536,7 +541,7 @@ class uds {
|
|
|
536
541
|
if (this.callback) {
|
|
537
542
|
this.callback(ctx, this, ['negative response', {'did':this.data.did,'didInfo':{'id':'','len':0},'val':''}]);
|
|
538
543
|
} else {
|
|
539
|
-
ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response
|
|
544
|
+
ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response reading did '+String(this.data.did)+'. Code=0x'+Number(candata[3]).toString(16));
|
|
540
545
|
}
|
|
541
546
|
await this.setDidDone(0);
|
|
542
547
|
break;
|
|
@@ -634,7 +639,7 @@ class uds {
|
|
|
634
639
|
if ( (candata[0] == 0x03) && (candata[1] == 0x7F) && (candata[2] == this.writeByDidProt.SIDtx) ) {
|
|
635
640
|
// Negative response
|
|
636
641
|
this.stat.cntCommNR += 1;
|
|
637
|
-
ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response
|
|
642
|
+
ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response writing did '+String(this.data.did)+'. Code=0x'+Number(candata[3]).toString(16));
|
|
638
643
|
await this.setDidDone(0);
|
|
639
644
|
break;
|
|
640
645
|
}
|
|
@@ -669,7 +674,7 @@ class uds {
|
|
|
669
674
|
if ( (candata[0] == 0x03) && (candata[1] == 0x7F) && (candata[2] == this.writeByDidProt.SIDtx) ) {
|
|
670
675
|
// Negative response
|
|
671
676
|
this.stat.cntCommNR += 1;
|
|
672
|
-
ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response
|
|
677
|
+
ctx.log.error('UDS worker error on '+this.config.stateBase+': Negative response writing did '+String(this.data.did)+'. Code=0x'+Number(candata[3]).toString(16));
|
|
673
678
|
await this.setDidDone(0);
|
|
674
679
|
break;
|
|
675
680
|
}
|
package/lib/codecs.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
const enums = require('./enums');
|
|
3
3
|
|
|
4
|
+
const C2POW08 = 0x100;
|
|
5
|
+
const C2POW15 = 0x08000;
|
|
6
|
+
const C2POW16 = 0x10000;
|
|
7
|
+
const C2POW24 = 0x1000000;
|
|
8
|
+
const C2POW31 = 0x080000000;
|
|
9
|
+
const C2POW32 = 0x100000000;
|
|
10
|
+
|
|
4
11
|
function arr2Hex(arr) {
|
|
5
12
|
// Convert byte array to hex string
|
|
6
13
|
let hs = '';
|
|
@@ -31,22 +38,22 @@ function sint08toVal(j, ofs=0) {
|
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
function uint16toVal(j, ofs=0) {
|
|
34
|
-
return
|
|
41
|
+
return C2POW08*j[ofs+1]+j[ofs];
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
function sint16toVal(j, ofs=0) {
|
|
38
|
-
let v =
|
|
39
|
-
if (v >=
|
|
45
|
+
let v = C2POW08*j[ofs+1]+j[ofs];
|
|
46
|
+
if (v >= C2POW15) { v -= C2POW16; }
|
|
40
47
|
return v;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
function uint32toVal(j, ofs=0) {
|
|
44
|
-
return
|
|
51
|
+
return C2POW24*j[ofs+3]+C2POW16*j[ofs+2]+C2POW08*j[ofs+1]+j[ofs];
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
function sint32toVal(j, ofs=0) {
|
|
48
|
-
let v =
|
|
49
|
-
if (v >=
|
|
55
|
+
let v = C2POW24*j[ofs+3]+C2POW16*j[ofs+2]+C2POW08*j[ofs+1]+j[ofs];
|
|
56
|
+
if (v >= C2POW31) { v -= C2POW32; }
|
|
50
57
|
return v;
|
|
51
58
|
}
|
|
52
59
|
|
|
@@ -67,7 +74,7 @@ function int2val(j, ofs = 0, signed = false) {
|
|
|
67
74
|
function val2byteArr(v, byte_width, scale = 1, signed = false) {
|
|
68
75
|
// Convert int08, int16, int32 to byte array. Signed or unsigned.
|
|
69
76
|
let val = Math.round(eval(v) * scale);
|
|
70
|
-
if ( (signed) && (val < 0) ) { val +=
|
|
77
|
+
if ( (signed) && (val < 0) ) { val += (2**(8*byte_width)); }
|
|
71
78
|
const string_bin = toByteArray(toHex(val, byte_width*2));
|
|
72
79
|
return string_bin.reverse();
|
|
73
80
|
}
|
|
@@ -586,5 +593,6 @@ const O3Ecodecs = {
|
|
|
586
593
|
module.exports = {
|
|
587
594
|
O3Ecodecs,
|
|
588
595
|
arr2Hex,
|
|
589
|
-
toByteArray
|
|
596
|
+
toByteArray,
|
|
597
|
+
val2byteArr
|
|
590
598
|
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for codecs
|
|
5
|
+
*
|
|
6
|
+
* It's automatically excluded from npm and its build output is excluded from both git and npm.
|
|
7
|
+
* It is advised to test all your modules with accompanying *.test.js-files
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// tslint:disable:no-unused-expression
|
|
11
|
+
|
|
12
|
+
const { expect } = require('chai');
|
|
13
|
+
const E3 = require('./codecs');
|
|
14
|
+
const E3DidsDict = require('./didsE3.json');
|
|
15
|
+
|
|
16
|
+
function toByteArray(hs) {
|
|
17
|
+
// Convert hex string, e.g. '21A8' to byte array: [33,168]
|
|
18
|
+
const ba = [];
|
|
19
|
+
for (let i=0; i<hs.length/2; i++) {
|
|
20
|
+
ba.push(parseInt(hs.slice(2*i,2*i+2), 16));
|
|
21
|
+
}
|
|
22
|
+
return ba;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function codecDecode(descr, expected, codec, len, id, args, val) {
|
|
26
|
+
it(`${descr} ${JSON.stringify(expected)}`, () => {
|
|
27
|
+
const f = new codec(len,id,args);
|
|
28
|
+
const result = f.decode(val);
|
|
29
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function codecEncode(descr, expected, codec, len, id, args, val) {
|
|
34
|
+
it(`${descr} ${JSON.stringify(expected)}`, () => {
|
|
35
|
+
const f = new codec(len,id,args);
|
|
36
|
+
const result = f.encode(val);
|
|
37
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// val2byteArray:
|
|
42
|
+
describe('codecs.js => val2byteArr()', () => {
|
|
43
|
+
let val = -1;
|
|
44
|
+
let expected = [0xff];
|
|
45
|
+
it(`sint08 ${JSON.stringify(expected)}`, () => {
|
|
46
|
+
const result = E3.val2byteArr(val, 4, 1, true);
|
|
47
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
48
|
+
});
|
|
49
|
+
val = 2**7-1;
|
|
50
|
+
expected = [0x7f];
|
|
51
|
+
it(`sint08 ${JSON.stringify(expected)}`, () => {
|
|
52
|
+
const result = E3.val2byteArr(val, 4, 1, true);
|
|
53
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
54
|
+
});
|
|
55
|
+
val = -(2**7);
|
|
56
|
+
expected = [0x80];
|
|
57
|
+
it(`sint08 ${JSON.stringify(expected)}`, () => {
|
|
58
|
+
const result = E3.val2byteArr(val, 4, 1, true);
|
|
59
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
60
|
+
});
|
|
61
|
+
val = 2**8-1;
|
|
62
|
+
expected = [0xff];
|
|
63
|
+
it(`uint08 ${JSON.stringify(expected)}`, () => {
|
|
64
|
+
const result = E3.val2byteArr(val, 4, 1, false);
|
|
65
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
val = -1;
|
|
69
|
+
expected = [0xff,0xff];
|
|
70
|
+
it(`sint16 ${JSON.stringify(expected)}`, () => {
|
|
71
|
+
const result = E3.val2byteArr(val, 4, 1, true);
|
|
72
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
73
|
+
});
|
|
74
|
+
val = 2**15-1;
|
|
75
|
+
expected = [0xff,0x7f];
|
|
76
|
+
it(`sint16 ${JSON.stringify(expected)}`, () => {
|
|
77
|
+
const result = E3.val2byteArr(val, 4, 1, true);
|
|
78
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
79
|
+
});
|
|
80
|
+
val = -(2**15);
|
|
81
|
+
expected = [0x00,0x80];
|
|
82
|
+
it(`sint16 ${JSON.stringify(expected)}`, () => {
|
|
83
|
+
const result = E3.val2byteArr(val, 4, 1, true);
|
|
84
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
85
|
+
});
|
|
86
|
+
val = 2**16-1;
|
|
87
|
+
expected = [0xff,0xff];
|
|
88
|
+
it(`uint16 ${JSON.stringify(expected)}`, () => {
|
|
89
|
+
const result = E3.val2byteArr(val, 4, 1, false);
|
|
90
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
val = -1;
|
|
94
|
+
expected = [0xff,0xff,0xff,0xff];
|
|
95
|
+
it(`sint32 ${JSON.stringify(expected)}`, () => {
|
|
96
|
+
const result = E3.val2byteArr(val, 4, 1, true);
|
|
97
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
98
|
+
});
|
|
99
|
+
val = 2**31-1;
|
|
100
|
+
expected = [0xff,0xff,0xff,0x7f];
|
|
101
|
+
it(`sint32 ${JSON.stringify(expected)}`, () => {
|
|
102
|
+
const result = E3.val2byteArr(val, 4, 1, true);
|
|
103
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
104
|
+
});
|
|
105
|
+
val = -(2**31);
|
|
106
|
+
expected = [0x00,0x00,0x00,0x80];
|
|
107
|
+
it(`sint32 ${JSON.stringify(expected)}`, () => {
|
|
108
|
+
const result = E3.val2byteArr(val, 4, 1, true);
|
|
109
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
110
|
+
});
|
|
111
|
+
val = 2**32-1;
|
|
112
|
+
expected = [0xff,0xff,0xff,0xff];
|
|
113
|
+
it(`uint32 ${JSON.stringify(expected)}`, () => {
|
|
114
|
+
const result = E3.val2byteArr(val, 4, 1, false);
|
|
115
|
+
expect(JSON.stringify(result)).to.equal(JSON.stringify(expected));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Integer DECODER:
|
|
120
|
+
describe('codecs.js => O3EInt8().decode()', () => {
|
|
121
|
+
codecDecode('Positive Number, scale=10, signed=true ', 5, E3.O3Ecodecs.O3EInt8, 1, 'test1', {scale:10.0,signed:true, offset: 0}, [0x32]);
|
|
122
|
+
codecDecode('Positive Number, scale=10, signed=true ', (2**7-1)/10, E3.O3Ecodecs.O3EInt8, 1, 'test2', {scale:10.0,signed:true, offset: 0}, [0x7f]);
|
|
123
|
+
codecDecode('Negative Number, scale=10, signed=true ', -(2**7)/10, E3.O3Ecodecs.O3EInt8, 1, 'test3', {scale:10.0,signed:true, offset: 0}, [0x80]);
|
|
124
|
+
codecDecode('Negative Number, scale= 1, signed=true ', -5, E3.O3Ecodecs.O3EInt8, 1, 'test4', {scale:1.0, signed:true, offset: 0}, [0xfb]);
|
|
125
|
+
codecDecode('Positive Number, scale=10, signed=false', 25.1, E3.O3Ecodecs.O3EInt8, 1, 'test5', {scale:10.0,signed:false,offset: 0}, [0xfb]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('codecs.js => O3EInt16().decode()', () => {
|
|
129
|
+
codecDecode('Positive Number, scale=10, signed=true ', 5, E3.O3Ecodecs.O3EInt16, 2, 'test1', {scale:10.0,signed:true, offset: 0}, [0x32,0x00]);
|
|
130
|
+
codecDecode('Positive Number, scale=10, signed=true ', (2**15-1)/10, E3.O3Ecodecs.O3EInt16, 2, 'test2', {scale:10.0,signed:true, offset: 0}, [0xff,0x7f]);
|
|
131
|
+
codecDecode('Negative Number, scale=10, signed=true ', -(2**15)/10, E3.O3Ecodecs.O3EInt16, 2, 'test3', {scale:10.0,signed:true, offset: 0}, [0x00,0x80]);
|
|
132
|
+
codecDecode('Negative Number, scale= 1, signed=true ', -5, E3.O3Ecodecs.O3EInt16, 2, 'test4', {scale:1.0, signed:true, offset: 0}, [0xfb,0xff]);
|
|
133
|
+
codecDecode('Positive Number, scale=10, signed=false', (2**16-1)/10, E3.O3Ecodecs.O3EInt16, 2, 'test5', {scale:10.0,signed:false,offset: 0}, [0xff,0xff]);
|
|
134
|
+
codecDecode('Positive Number, scale=10, signed=false', 25.1, E3.O3Ecodecs.O3EInt16, 2, 'test6', {scale:10.0,signed:false,offset: 0}, [0xfb,0x00]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('codecs.js => O3EInt32().decode()', () => {
|
|
138
|
+
codecDecode('Positive Number, scale=10, signed=true ', 5, E3.O3Ecodecs.O3EInt32, 4, 'test1', {scale:10.0,signed:true, offset: 0}, [0x32,0x00,0x00,0x00]);
|
|
139
|
+
codecDecode('Positive Number, scale=10, signed=true ', (2**31-1)/10, E3.O3Ecodecs.O3EInt32, 4, 'test2', {scale:10.0,signed:true, offset: 0}, [0xff,0xff,0xff,0x7f]);
|
|
140
|
+
codecDecode('Negative Number, scale=10, signed=true ', -(2**31)/10, E3.O3Ecodecs.O3EInt32, 4, 'test3', {scale:10.0,signed:true, offset: 0}, [0x00,0x00,0x00,0x80]);
|
|
141
|
+
codecDecode('Negative Number, scale= 1, signed=true ', -5, E3.O3Ecodecs.O3EInt32, 4, 'test4', {scale:1.0, signed:true, offset: 0}, [0xfb,0xff,0xff,0xff]);
|
|
142
|
+
codecDecode('Positive Number, scale=10, signed=false', (2**32-1)/10, E3.O3Ecodecs.O3EInt32, 4, 'test5', {scale:10.0,signed:false,offset: 0}, [0xff,0xff,0xff,0xff]);
|
|
143
|
+
codecDecode('Positive Number, scale=10, signed=false', 25.1, E3.O3Ecodecs.O3EInt32, 4, 'test6', {scale:10.0,signed:false,offset: 0}, [0xfb,0x00,0x00,0x00]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Integer ENCODER:
|
|
147
|
+
describe('codecs.js => O3EInt8().encode()', () => {
|
|
148
|
+
codecEncode('Positive Number, scale=10, signed=true ', [0x32], E3.O3Ecodecs.O3EInt8, 1, 'test1', {scale:10.0,signed:true, offset: 0}, 5);
|
|
149
|
+
codecEncode('Positive Number, scale=10, signed=true ', [0x7f], E3.O3Ecodecs.O3EInt8, 1, 'test2', {scale:10.0,signed:true, offset: 0}, (2**7-1)/10);
|
|
150
|
+
codecEncode('Negative Number, scale=10, signed=true ', [0x80], E3.O3Ecodecs.O3EInt8, 1, 'test3', {scale:10.0,signed:true, offset: 0}, -(2**7)/10);
|
|
151
|
+
codecEncode('Negative Number, scale= 1, signed=true ', [0xfb], E3.O3Ecodecs.O3EInt8, 1, 'test4', {scale:1.0, signed:true, offset: 0}, -5);
|
|
152
|
+
codecEncode('Positive Number, scale=10, signed=false', [0xfb], E3.O3Ecodecs.O3EInt8, 1, 'test5', {scale:10.0,signed:false,offset: 0}, 25.1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('codecs.js => O3EInt16().decode()', () => {
|
|
156
|
+
codecEncode('Positive Number, scale=10, signed=true ', [0x32,0x00], E3.O3Ecodecs.O3EInt16, 2, 'test1', {scale:10.0,signed:true, offset: 0}, 5);
|
|
157
|
+
codecEncode('Positive Number, scale=10, signed=true ', [0xff,0x7f], E3.O3Ecodecs.O3EInt16, 2, 'test2', {scale:10.0,signed:true, offset: 0}, (2**15-1)/10);
|
|
158
|
+
codecEncode('Negative Number, scale=10, signed=true ', [0x00,0x80], E3.O3Ecodecs.O3EInt16, 2, 'test3', {scale:10.0,signed:true, offset: 0}, -(2**15)/10);
|
|
159
|
+
codecEncode('Negative Number, scale= 1, signed=true ', [0xfb,0xff], E3.O3Ecodecs.O3EInt16, 2, 'test4', {scale:1.0, signed:true, offset: 0}, -5);
|
|
160
|
+
codecEncode('Positive Number, scale=10, signed=false', [0xff,0xff], E3.O3Ecodecs.O3EInt16, 2, 'test5', {scale:10.0,signed:false,offset: 0}, (2**16-1)/10);
|
|
161
|
+
codecEncode('Positive Number, scale=10, signed=false', [0xfb,0x00], E3.O3Ecodecs.O3EInt16, 2, 'test6', {scale:10.0,signed:false,offset: 0}, 25.1);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('codecs.js => O3EInt32().encode()', () => {
|
|
165
|
+
codecEncode('Positive Number, scale=10, signed=true ', [0x32,0x00,0x00,0x00], E3.O3Ecodecs.O3EInt32, 4, 'test1', {scale:10.0,signed:true, offset: 0}, 5);
|
|
166
|
+
codecEncode('Positive Number, scale=10, signed=true ', [0xff,0xff,0xff,0x7f], E3.O3Ecodecs.O3EInt32, 4, 'test2', {scale:10.0,signed:true, offset: 0}, (2**31-1)/10);
|
|
167
|
+
codecEncode('Negative Number, scale=10, signed=true ', [0x00,0x00,0x00,0x80], E3.O3Ecodecs.O3EInt32, 4, 'test3', {scale:10.0,signed:true, offset: 0}, -(2**31)/10);
|
|
168
|
+
codecEncode('Negative Number, scale= 1, signed=true ', [0xfb,0xff,0xff,0xff], E3.O3Ecodecs.O3EInt32, 4, 'test4', {scale:1.0, signed:true, offset: 0}, -5);
|
|
169
|
+
codecEncode('Positive Number, scale=10, signed=false', [0xff,0xff,0xff,0xff], E3.O3Ecodecs.O3EInt32, 4, 'test5', {scale:10.0,signed:false,offset: 0}, (2**32-1)/10);
|
|
170
|
+
codecEncode('Positive Number, scale=10, signed=false', [0xfb,0x00,0x00,0x00], E3.O3Ecodecs.O3EInt32, 4, 'test6', {scale:10.0,signed:false,offset: 0}, 25.1);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Complex data structures
|
|
174
|
+
// Do testing for specific dids to cover all available codecs
|
|
175
|
+
describe('codecs.js => O3EComplexType()', () => {
|
|
176
|
+
const raw_hex = '3b0206004700fd01c30801000300f9013001020030303030303030303030303030383135';
|
|
177
|
+
const raw_arr = toByteArray(raw_hex);
|
|
178
|
+
const js = {'BusAddress':59,'BusType':{'ID':2,'Text':'CanInternal'},'DeviceProperty':{'ID':6,'Text':'HMI'},'DeviceFunction':{'ID':0,'Text':'NOTHING'},'SW-Version':'71.509.2243.1','HW-Version':'3.505.304.2','VIN':'0000000000000815'};
|
|
179
|
+
const cdi = E3DidsDict[256];
|
|
180
|
+
codecDecode('Decoding did 256 ', js, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, raw_arr);
|
|
181
|
+
// Encoding makes no sense for this did in real life, but testing helps to improve codec robustness
|
|
182
|
+
codecEncode('Encoding did 256 ', raw_arr, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, js);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('codecs.js => O3EList()', () => {
|
|
186
|
+
const raw_hex = '0a00b001f30314170b12050e3a313602f30314170b12050e390c3602f30314170b12050e2f273602f30314170b12050d2b063602f30314170b12050d2a233602f30314170b12050d27383602f30314170b12050d26033602f30314170b12050d242d3602f30314170b12050d1f043602f30314170b12050d1e133602';
|
|
187
|
+
const raw_arr = toByteArray(raw_hex);
|
|
188
|
+
const js = {'Count':10,'GrandTotal':432,'ListEntries':[{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 14:58:49','Timestamp':1700315929000},'Unknown':566},{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 14:57:12','Timestamp':1700315832000},'Unknown':566},{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 14:47:39','Timestamp':1700315259000},'Unknown':566},{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 13:43:06','Timestamp':1700311386000},'Unknown':566},{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 13:42:35','Timestamp':1700311355000},'Unknown':566},{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 13:39:56','Timestamp':1700311196000},'Unknown':566},{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 13:38:03','Timestamp':1700311083000},'Unknown':566},{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 13:36:45','Timestamp':1700311005000},'Unknown':566},{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 13:31:04','Timestamp':1700310664000},'Unknown':566},{'Error':{'ID':1011,'Text':'FailureCompressorOutletPressureSensor'},'DateTime':{'DateTime':'18/11/2023, 13:30:19','Timestamp':1700310619000},'Unknown':566}]};
|
|
189
|
+
const cdi = E3DidsDict[266];
|
|
190
|
+
codecDecode('Decoding did 266 ', js, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, raw_arr);
|
|
191
|
+
// Encoding makes no sense for this did in real life, but testing helps to improve codec robustness
|
|
192
|
+
codecEncode('Encoding did 266 ', raw_arr, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, js);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('codecs.js => O3EComplexType()', () => {
|
|
196
|
+
const raw_hex = '8c01c1007a027e0100';
|
|
197
|
+
const raw_arr = toByteArray(raw_hex);
|
|
198
|
+
const js = {'Actual':39.6,'Minimum':19.3,'Maximum':63.4,'Average':38.2,'Unknown':0};
|
|
199
|
+
const cdi = E3DidsDict[268];
|
|
200
|
+
codecDecode('Decoding did 268 ', js, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, raw_arr);
|
|
201
|
+
// Encoding makes no sense for this did in real life, but testing helps to improve codec robustness
|
|
202
|
+
codecEncode('Encoding did 268 ', raw_arr, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, js);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('codecs.js => O3EList()', () => {
|
|
206
|
+
const raw_hex = '020800091e0000030f001100000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
|
|
207
|
+
const raw_arr = toByteArray(raw_hex);
|
|
208
|
+
const js = {'Count':2,'Schedules':[{'Start':'08:00:00','Stop':'09:30:00','Unknown':'0000','Mode':3},{'Start':'15:00:00','Stop':'17:00:00','Unknown':'0000','Mode':3}]};
|
|
209
|
+
const cdi = E3DidsDict[726];
|
|
210
|
+
codecDecode('Decoding did 726 ', js, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, raw_arr);
|
|
211
|
+
codecEncode('Encoding did 726 ', raw_arr, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, js);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('codecs.js => O3EComplexType()', () => {
|
|
215
|
+
const raw_hex = '38004f0068005c004b0045005400650059000602780090003c004c004b007400840058000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000028002f004200';
|
|
216
|
+
const raw_arr = toByteArray(raw_hex);
|
|
217
|
+
const js = {'CurrentMonth':{'10':51.8,'11':12,'12':14.4,'13':6,'14':7.6,'15':7.5,'16':11.6,'17':13.2,'18':8.8,'19':0,'20':0,'21':0,'22':0,'23':0,'24':0,'25':0,'26':0,'27':0,'28':0,'29':0,'30':0,'31':0,'01':5.6,'02':7.9,'03':10.4,'04':9.2,'05':7.5,'06':6.9,'07':8.4,'08':10.1,'09':8.9},'LastMonth':{'10':0,'11':0,'12':0,'13':0,'14':0,'15':0,'16':0.4,'17':0,'18':0,'19':0,'20':0,'21':0,'22':0,'23':0,'24':0,'25':0,'26':0,'27':0,'28':0,'29':4,'30':4.7,'31':6.6,'01':0,'02':0,'03':0,'04':0,'05':0,'06':0,'07':0,'08':0,'09':0}};
|
|
218
|
+
const cdi = E3DidsDict[1294];
|
|
219
|
+
codecDecode('Decoding did 1294 ', js, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, raw_arr);
|
|
220
|
+
// Encoding makes no sense for this did in real life, but testing helps to improve codec robustness
|
|
221
|
+
codecEncode('Encoding did 1294 ', raw_arr, E3.O3Ecodecs[cdi.codec], cdi.len, cdi.id, cdi.args, js);
|
|
222
|
+
});
|
package/lib/didsE3Writables.json
CHANGED
|
@@ -13,14 +13,18 @@
|
|
|
13
13
|
"434": "MixerSixCircuitRoomTemperatureSetpoint",
|
|
14
14
|
"435": "VentilationStageTargetVolumeFlow",
|
|
15
15
|
"436": "MixerSevenCircuitRoomTemperatureSetpoint",
|
|
16
|
+
"437": "BypassOperationState",
|
|
16
17
|
"438": "MixerEightCircuitRoomTemperatureSetpoint",
|
|
17
18
|
"451": "ExternalAlternatingCurrentPowerSetpoint",
|
|
19
|
+
"491": "DomesticHotWaterCirculationPump",
|
|
20
|
+
"497": "DomesticHotWaterCirculationPumpMode",
|
|
18
21
|
"504": "DomesticHotWaterSetpointMetaData",
|
|
19
22
|
"508": "UniversalTimeCoordinatedOffset",
|
|
20
23
|
"524": "ModulationTargetSetpoint",
|
|
21
24
|
"525": "ExternalModulationSetpoint",
|
|
22
25
|
"527": "FlowTemperatureTargetSetpoint",
|
|
23
26
|
"528": "ExternalTargetFlowTemperatureSetpoint",
|
|
27
|
+
"531": "DomesticHotWaterOperationState",
|
|
24
28
|
"533": "VentilationTargetOperationLevel",
|
|
25
29
|
"537": "ExternalMixerOneCircuitTargetOperationMode",
|
|
26
30
|
"538": "ExternalDomesticHotWaterTargetOperationMode",
|
|
@@ -79,10 +83,34 @@
|
|
|
79
83
|
"1009": "MixerTwoCircuitTargetQuickMode",
|
|
80
84
|
"1010": "MixerThreeCircuitTargetQuickMode",
|
|
81
85
|
"1011": "MixerFourCircuitTargetQuickMode",
|
|
86
|
+
"1084": "FlowTemperatureMinimumMaximumLimit",
|
|
87
|
+
"1100": "CentralHeatingPumpMinimumMaximumLimit",
|
|
88
|
+
"1101": "DomesticHotWaterPumpMinimumMaximumLimit",
|
|
89
|
+
"1102": "MixerOneCircuitPumpMinimumMaximumLimit",
|
|
90
|
+
"1103": "MixerTwoCircuitPumpMinimumMaximumLimit",
|
|
91
|
+
"1104": "MixerThreeCircuitPumpMinimumMaximumLimit",
|
|
92
|
+
"1105": "MixerFourCircuitPumpMinimumMaximumLimit",
|
|
93
|
+
"1118": "SolarCircuitPumpMinimumMaximumLimit",
|
|
82
94
|
"1167": "ExternalDomesticHotWaterTemperatureSetpoint",
|
|
95
|
+
"1192": "MixerOneCircuitFlowTemperatureMinimumMaximumLimit",
|
|
96
|
+
"1193": "MixerTwoCircuitFlowTemperatureMinimumMaximumLimit",
|
|
97
|
+
"1194": "MixerThreeCircuitFlowTemperatureMinimumMaximumLimit",
|
|
98
|
+
"1195": "MixerFourCircuitFlowTemperatureMinimumMaximumLimit",
|
|
99
|
+
"1196": "MixerFiveCircuitFlowTemperatureMinimumMaximumLimit",
|
|
100
|
+
"1197": "MixerSixCircuitFlowTemperatureMinimumMaximumLimit",
|
|
101
|
+
"1198": "MixerSevenCircuitFlowTemperatureMinimumMaximumLimit",
|
|
102
|
+
"1199": "MixerEightCircuitFlowTemperatureMinimumMaximumLimit",
|
|
83
103
|
"1265": "FanTargetSpeedMeta",
|
|
84
104
|
"1363": "FuelCellTargetOperationMode",
|
|
85
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",
|
|
86
114
|
"1439": "NoiseReductionTimeScheduleMonday",
|
|
87
115
|
"1440": "NoiseReductionTimeScheduleTuesday",
|
|
88
116
|
"1441": "NoiseReductionTimeScheduleWednesday",
|
|
@@ -91,6 +119,7 @@
|
|
|
91
119
|
"1444": "NoiseReductionTimeScheduleSaturday",
|
|
92
120
|
"1445": "NoiseReductionTimeScheduleSunday",
|
|
93
121
|
"1505": "SolarStagnationTemperatureOffset",
|
|
122
|
+
"1552": "ElectricalEnergyStorageOperationState",
|
|
94
123
|
"1555": "MixerOneCircuitFixedValueFlowTemperatureSetpoint",
|
|
95
124
|
"1556": "MixerTwoCircuitFixedValueFlowTemperatureSetpoint",
|
|
96
125
|
"1557": "MixerThreeCircuitFixedValueFlowTemperatureSetpoint",
|
|
@@ -103,6 +132,8 @@
|
|
|
103
132
|
"1587": "ExternalAlternatingCurrentPowerSetpointMetaData",
|
|
104
133
|
"1588": "AlternatingCurrentPowerSetpoint",
|
|
105
134
|
"1589": "AlternatingCurrentPowerSetpointMetaData",
|
|
135
|
+
"1590": "ElectricalEnergySystemOperationState",
|
|
136
|
+
"1591": "ElectricalEnergyInverterOperationState",
|
|
106
137
|
"1604": "GatewayExternalTargetFlowTemperatureSetpoint",
|
|
107
138
|
"1605": "GatewayExternalHeatEngineTargetOperationMode",
|
|
108
139
|
"1612": "ExternalMixerTwoCircuitTargetOperationMode",
|
|
@@ -355,6 +386,7 @@
|
|
|
355
386
|
"2327": "VentilationTargetVolumeFlow",
|
|
356
387
|
"2345": "CompressorSetpointPercent",
|
|
357
388
|
"2353": "TargetDemandHeatProducer",
|
|
389
|
+
"2372": "VentilationControllerOperationState",
|
|
358
390
|
"2373": "VentilationAirVolumeFlowBalancingOffset",
|
|
359
391
|
"2417": "MixerOneCircuitTargetValueRelativeHumidityCooling",
|
|
360
392
|
"2418": "MixerTwoCircuitTargetValueRelativeHumidityCooling",
|
|
@@ -382,6 +414,7 @@
|
|
|
382
414
|
"2878": "SubcoolingSetpoint",
|
|
383
415
|
"2883": "ExpansionValveMaximumOperatingPressureTemperatureSetpoint",
|
|
384
416
|
"2985": "ExternalHeaterTemperatureSetpoint",
|
|
417
|
+
"2986": "ExternalHeaterOperationState",
|
|
385
418
|
"3037": "ElectricityPriceTimeScheduleMonday",
|
|
386
419
|
"3038": "ElectricityPriceTimeScheduleTuesday",
|
|
387
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
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/*
|
|
4
|
+
* Unveil life data of Viessmann E3 series devices via CAN bus
|
|
5
|
+
*
|
|
6
|
+
* Based on project open3e: https://github.com/open3e/open3e
|
|
7
|
+
*
|
|
8
|
+
*/
|
|
9
|
+
|
|
3
10
|
/*
|
|
4
11
|
* Created with @iobroker/create-adapter v2.5.0
|
|
5
12
|
*/
|
|
@@ -8,9 +15,7 @@
|
|
|
8
15
|
// you need to create an adapter
|
|
9
16
|
const utils = require('@iobroker/adapter-core');
|
|
10
17
|
|
|
11
|
-
//
|
|
12
|
-
// const fs = require("fs");
|
|
13
|
-
|
|
18
|
+
// Loading modules:
|
|
14
19
|
const can = require('socketcan');
|
|
15
20
|
const storage = require('./lib/storage');
|
|
16
21
|
const collect = require('./lib/canCollect');
|
|
@@ -28,7 +33,8 @@ class E3oncan extends utils.Adapter {
|
|
|
28
33
|
name: 'e3oncan',
|
|
29
34
|
});
|
|
30
35
|
|
|
31
|
-
this.
|
|
36
|
+
this.stoppingInstance = false; // true during unLoad()
|
|
37
|
+
this.e380Collect = null; // E380 always is assigned to external bus
|
|
32
38
|
this.E3CollectInt = {}; // Dict of collect devices on internal bus
|
|
33
39
|
this.E3CollectExt = {}; // Dict of collect devices on external bus
|
|
34
40
|
this.collectTimeout = 1500; // Timeout (ms) for collecting data
|
|
@@ -38,6 +44,8 @@ class E3oncan extends utils.Adapter {
|
|
|
38
44
|
this.channelExtName = '';
|
|
39
45
|
this.channelInt = null;
|
|
40
46
|
this.channelIntName = '';
|
|
47
|
+
this.cntCanConnDesired = 0; // Number of activated CAN connections in config
|
|
48
|
+
this.cntCanConnActual = 0; // Number if actualy connected CAN buses
|
|
41
49
|
|
|
42
50
|
this.udsWorkers = {};
|
|
43
51
|
this.udsTimeout = 7500; // Timeout (ms) for normal UDS communication
|
|
@@ -84,8 +92,9 @@ class E3oncan extends utils.Adapter {
|
|
|
84
92
|
|
|
85
93
|
// @ts-ignore
|
|
86
94
|
if (this.config.canExtActivated) {
|
|
95
|
+
this.cntCanConnDesired++;
|
|
87
96
|
// @ts-ignore
|
|
88
|
-
[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);
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
// Setup internal CAN bus if required
|
|
@@ -93,8 +102,14 @@ class E3oncan extends utils.Adapter {
|
|
|
93
102
|
|
|
94
103
|
// @ts-ignore
|
|
95
104
|
if (this.config.canIntActivated) {
|
|
105
|
+
this.cntCanConnDesired++;
|
|
96
106
|
// @ts-ignore
|
|
97
|
-
[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);
|
|
98
113
|
}
|
|
99
114
|
|
|
100
115
|
// Setup E380 collect worker:
|
|
@@ -114,14 +129,15 @@ class E3oncan extends utils.Adapter {
|
|
|
114
129
|
|
|
115
130
|
// Setup CAN busses
|
|
116
131
|
|
|
117
|
-
async connectToCan(channel, name, onMsg) {
|
|
132
|
+
async connectToCan(channel, name, onMsg, onStop) {
|
|
118
133
|
let chName = name;
|
|
119
134
|
if (!channel) {
|
|
120
135
|
try {
|
|
121
136
|
channel = can.createRawChannel(name, true);
|
|
122
137
|
await channel.addListener('onMessage', onMsg, this);
|
|
138
|
+
await channel.addListener('onStopped', onStop, this);
|
|
123
139
|
await channel.start();
|
|
124
|
-
|
|
140
|
+
this.cntCanConnActual++;
|
|
125
141
|
await this.log.info('CAN-Adapter connected: '+name);
|
|
126
142
|
} catch (e) {
|
|
127
143
|
await this.log.error(`Could not connect to CAN-Adapter "${name}" - err=${e.message}`);
|
|
@@ -143,7 +159,6 @@ class E3oncan extends utils.Adapter {
|
|
|
143
159
|
channel = null;
|
|
144
160
|
}
|
|
145
161
|
}
|
|
146
|
-
return([channel,'']);
|
|
147
162
|
}
|
|
148
163
|
|
|
149
164
|
// Setup E380 collect worker:
|
|
@@ -242,6 +257,7 @@ class E3oncan extends utils.Adapter {
|
|
|
242
257
|
*/
|
|
243
258
|
async onUnload(callback) {
|
|
244
259
|
try {
|
|
260
|
+
this.stoppingInstance = true;
|
|
245
261
|
// Stop UDS workers:
|
|
246
262
|
for (const worker of Object.values(this.E3UdsWorkers)) await worker.stop(this);
|
|
247
263
|
for (const worker of Object.values(this.udsScanWorker.workers)) await worker.stop(this);
|
|
@@ -256,6 +272,7 @@ class E3oncan extends utils.Adapter {
|
|
|
256
272
|
await this.disconnectFromCan(this.channelExt,this.config.canExtName);
|
|
257
273
|
// @ts-ignore
|
|
258
274
|
await this.disconnectFromCan(this.channelInt,this.config.canIntName);
|
|
275
|
+
this.setState('info.connection', false, true);
|
|
259
276
|
|
|
260
277
|
callback();
|
|
261
278
|
} catch (e) {
|
|
@@ -450,6 +467,24 @@ class E3oncan extends utils.Adapter {
|
|
|
450
467
|
}
|
|
451
468
|
}
|
|
452
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
|
+
|
|
453
488
|
onCanMsgExt(msg) {
|
|
454
489
|
if ( (this.e380Collect) && (this.e380Collect.config.canID.includes(msg.id)) ) { this.e380Collect.msgCollect(this, msg); }
|
|
455
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.
|
|
3
|
+
"version": "0.6.14",
|
|
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",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "
|
|
21
|
+
"url": "https://github.com/MyHomeMyData/ioBroker.e3oncan.git"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">= 16"
|