iobroker.vds2465-server
Version:
Empfänger von VdS2465-Meldungen
1,297 lines (1,229 loc) • 43 kB
JavaScript
'use strict';
const eventhandler = require('events');
const crypto = require('crypto');
const _level = ['silly', 'debug', 'info', 'warn', 'error'];
const _action = {
Connect: 1,
Daten: 2,
Disconnect: 3,
IK3: 4,
IK4: 5,
IK5: 6,
IK6: 7,
Checksummenfehler: 8,
IK3_nach_Pollzeit: 9,
Timer_abgelaufen: 10,
Wiederholung: 11,
VdSServiceRequest: 12,
};
/**
*class vds2465server
*/
class vds2465server extends eventhandler {
#TC;
#RC_rec;
#RC;
#TC_rec;
#IK;
#PK;
#SL;
#L;
#VdSRequestCounter;
#LastSend;
#SendeZaehler;
#Socket;
#AddressPort;
#Algorithm;
#MinLaenge;
#Loglevel;
#SendeSpeicher;
#EmpfangsSpeicher;
#Inhalt;
#KeyNr_rec;
#TimerAnswer;
#TimerPoll;
#Polling;
#UnbekannterKey;
#Stehend;
#KeyNr;
#Key;
#Id;
#KeyList;
/**
* Konstruktor
*/
constructor() {
super();
this.#TC = Math.floor(Math.random() * 0xffffffff);
this.#SendeZaehler = 0;
this.#Socket = null;
this.#AddressPort = '';
this.#Algorithm = 'aes-128-cbc';
this.#MinLaenge = 48;
this.#Loglevel = 'info';
this.#TimerAnswer = null;
this.#TimerPoll = null;
this.#Polling = 8000;
this.#UnbekannterKey = false;
this.#SendeSpeicher = [];
this.#EmpfangsSpeicher = Buffer.alloc(0);
this.#Inhalt = {};
this.#KeyNr_rec = 0;
this.#VdSRequestCounter = 0;
this.#Stehend = false;
this.#KeyNr = 0;
this.#Key = '';
this.#Id = 0;
this.#KeyList = [];
this.connect = this.connect.bind(this);
this.disconnect = this.disconnect.bind(this);
this.received = this.received.bind(this);
}
/**
* Setzt den Pollintervall
*
* @param {number} value Der Pollintervall zwischen 3 und 8 Sekunden
*/
set Polling(value) {
if (value >= 3 && value <= 8) {
this.#Polling = value * 1000;
}
}
/**
* Gibt die Identnummer der Verbindung zurueck
*
* @returns {string} die Identnummer
*/
get Identnummer() {
if (this.#Id === 0) {
return '';
}
return this.#Id.toString();
}
/**
* Gibt ein Zufalls-Schluessel AES128 zurueck
*
* @returns {string} der Schluessel
*/
static getRandomKey() {
return crypto.randomBytes(16).toString('hex');
}
/**
* Loescht alle Geraete
*/
delAllDevices() {
this.#KeyList = [];
}
/**
* Setzt die Geraetedaten fuer den Empfang
*
* @param {string | number} identnr die Identnummer
* @param {boolean} stehend true bei stehender Verbindung
* @param {number} keynr Die Schluesselnummer von 0 bis 65534
* @param {string} key Der Schluessel in hexadezimal mit einer Laenge von 16 Byte
*/
addDevice(identnr, stehend, keynr, key = '') {
try {
const obj = {};
obj.identnr = '999999999999';
if (typeof identnr === 'string' && identnr.length <= 12) {
obj.identnr = identnr;
} else if (typeof identnr === 'number') {
obj.identnr = identnr.toString();
}
obj.stehend = false;
if (typeof stehend === 'boolean' && stehend) {
obj.stehend = true;
}
obj.keynr = 0;
if (typeof keynr === 'number' && keynr >= 0 && keynr < 65535) {
obj.keynr = keynr;
}
obj.key = '';
if (key.length === 32) {
const tmp = key.toLowerCase();
const reg = new RegExp('[0-9a-f]{32}');
if (reg.test(tmp)) {
obj.key = tmp;
}
}
if (this.#KeyList.length > 0) {
let findindex = -1;
for (let i = 0; i < this.#KeyList.length; i++) {
if (this.#KeyList[i].keynr === obj.keynr) {
findindex = i;
break;
}
}
if (findindex === -1) {
this.#KeyList.push(obj);
} else {
this.#KeyList[findindex] = obj;
}
} else {
this.#KeyList.push(obj);
}
this.#logging(`Geraet uebernommen: ${JSON.stringify(obj)}`, 'debug');
} catch (e) {
this.#logging(`addDevice: ${e}`, 'error');
}
}
/**
* Startet Kommunikation
*
* @param {net.Socket} sock Der Socket zum Geraet
*/
connect(sock) {
this.#Socket = sock;
this.#AddressPort = `${sock.remoteAddress}:${sock.remotePort}`;
this.#controller(_action.Connect);
}
/**
* Sendet den Datensatz ans Geraet
*
* @param {Buffer | string} data Der vollstaendige Datensatz zum Senden
*/
sendCommand(data) {
if (typeof data === 'string') {
const buf = Buffer.from(data, 'hex');
this.#SendeSpeicher.push(buf);
} else {
this.#SendeSpeicher.push(data);
}
}
/**
* Beendet die Kommunikation
*/
disconnect() {
this.#controller(_action.Disconnect);
}
/**
* Uebernimmt Daten vom Geraet
*
* @param {Buffer} data Die empfangenden Daten
*/
received(data) {
this.#EmpfangsSpeicher = Buffer.concat([this.#EmpfangsSpeicher, Buffer.from(data)]);
this.#controller(_action.Daten);
}
/**
* Setzt den LogLevel
*
* @param {string} target log.level
*/
setLogLevel(target) {
if (typeof target === 'string' && _level.indexOf(target) >= 0) {
this.#Loglevel = target;
}
}
/**
* Trennt die Verbindung und setzt alles zurueck
*/
#disconnect() {
if (this.#Socket) {
this.#Socket.end();
this.#Socket = null;
}
if (this.#TimerAnswer) {
clearTimeout(this.#TimerAnswer);
this.#TimerAnswer = null;
}
if (this.#TimerPoll) {
clearTimeout(this.#TimerPoll);
this.#TimerPoll = null;
}
if (this.#Id) {
this.emit('disconnect', { id: this.#Id.toString(), address: this.#AddressPort });
}
this.#TC = Math.floor(Math.random() * 0xffffffff);
this.#SendeSpeicher = [];
this.#EmpfangsSpeicher = Buffer.alloc(0);
this.#LastSend = null;
this.#SendeZaehler = 0;
this.#UnbekannterKey = false;
this.#Id = 0;
this.#Inhalt = {};
this.#KeyNr_rec = 0;
this.#KeyList = [];
this.#AddressPort = '';
}
/**
* Steuert die Ablaeufe
*
* @param {number} func Aktuelle Funktion
*/
#controller(func) {
if (!func) {
return;
}
let data;
switch (func) {
case _action.Connect:
this.#sendIK1();
this.#timer();
break;
case _action.Daten:
if (this.#LaengePruefen(this.#EmpfangsSpeicher)) {
if (this.#SchluesselPruefen(this.#EmpfangsSpeicher)) {
data = this.#cutDatensatz();
this.#logging(`in: ${data.toString('hex')}`, 'silly');
if (this.#EmpfangsSpeicher.length > 0) {
this.#logging(`Rest Speicher: ${this.#EmpfangsSpeicher.toString('hex')}`, 'silly');
}
if (this.#Auswertung(data)) {
//clearTimeout(this._Timer);
}
} else {
this.#logging(`Schluesselnr ${this.#KeyNr_rec} ist unbekannt`, 'error');
this.#disconnect();
}
}
break;
case _action.Disconnect:
this.#disconnect();
break;
case _action.IK3:
this.#sendIK3();
this.#timer();
break;
case _action.IK4:
if (this.#TimerPoll) {
clearTimeout(this.#TimerPoll);
}
this.#sendeMeldung();
this.#timer();
break;
case _action.IK5:
this.#sendIK5();
this.#sendIK3();
this.#timer();
break;
case _action.IK6:
this.#sendIK6();
this.#sendIK3();
this.#timer();
break;
case _action.VdSServiceRequest:
this.#VdSRequestCounter = 5;
if (this.#TimerPoll) {
clearTimeout(this.#TimerPoll);
}
this.#sendIK3();
break;
case _action.IK3_nach_Pollzeit:
if (this.#TimerPoll) {
clearTimeout(this.#TimerPoll);
}
if (this.#VdSRequestCounter > 0) {
this.#sendIK3();
this.#VdSRequestCounter--;
} else {
this.#TimerPoll = setTimeout(this.#controller.bind(this), this.#Polling, _action.IK3);
}
break;
case _action.Timer_abgelaufen:
case _action.Wiederholung:
if (this.#TimerPoll) {
clearTimeout(this.#TimerPoll);
}
this.#SendeZaehler++;
if (this.#SendeZaehler > 3) {
this.#disconnect();
return;
}
this.#logging('Wiederhole letztes Telegramm', 'debug');
this.#sendLastMessage();
this.#timer();
break;
default:
break;
}
}
//Schneidet den Datensatz aus dem Speicher und gibt ihn zurueck
#cutDatensatz() {
const sl = this.#EmpfangsSpeicher.readUInt16BE(2);
const data = this.#EmpfangsSpeicher.slice(0, sl + 4);
if (sl + 4 < this.#EmpfangsSpeicher.length) {
this.#EmpfangsSpeicher = this.#EmpfangsSpeicher.slice(sl + 4);
} else {
this.#EmpfangsSpeicher = Buffer.alloc(0);
}
return data;
}
#logging(msg, level = 'info') {
if (_level.indexOf(level) >= _level.indexOf(this.#Loglevel)) {
this.emit('log', msg, level);
}
}
//Timer fuer das ausbleiben einer Antwort
#timer() {
if (this.#TimerAnswer) {
clearTimeout(this.#TimerAnswer);
}
this.#TimerAnswer = setTimeout(this.#controller.bind(this), this.#Polling + 1000, _action.Timer_abgelaufen);
}
#send(buf) {
if (this.#Socket) {
this.#LastSend = Buffer.from(buf);
this.#Socket.write(this.#LastSend, 'hex');
}
}
//Sendet letzte Meldung
#sendLastMessage() {
if (this.#Socket) {
this.#Socket.write(this.#LastSend, 'hex');
}
}
#sendIK1() {
let offset = 0;
let buf = Buffer.alloc(14);
//TC
offset = buf.writeUInt32BE(this.#TC++, offset);
if (this.#TC > 0xffffffff) {
this.#TC = 0;
}
//CRC16
offset = buf.writeUInt16BE(0, offset);
//RC
offset = buf.writeUInt32BE(0, offset);
//IK
offset = buf.writeUInt8(1, offset);
//PK
offset = buf.writeUInt8(1, offset);
//L
offset = buf.writeUInt8(1, offset);
//Fenstergroesse
buf.writeUInt8(1, offset);
buf = this.#Telegramm_Zusatz(buf);
this.#logging(`out IK1: ${buf.toString('hex')}`, 'silly');
this.#send(buf);
}
#sendIK3() {
let offset = 0;
let buf = Buffer.alloc(13);
//TC
offset = buf.writeUInt32BE(this.#TC++, offset);
if (this.#TC > 0xffffffff) {
this.#TC = 0;
}
//CRC16
offset = buf.writeUInt16BE(0, offset); //sp�ter
//RC
this.#RC = this.#TC_rec + 1;
if (this.#RC > 0xffffffff) {
this.#RC = 0;
}
offset = buf.writeUInt32BE(this.#RC, offset);
//IK
offset = buf.writeUInt8(3, offset);
//PK
offset = buf.writeUInt8(1, offset);
//L
buf.writeUInt8(0, offset);
buf = this.#Telegramm_Zusatz(buf);
this.#logging(`out IK3: ${buf.toString('hex')}`, 'silly');
this.#send(buf);
}
#sendIK5() {
let offset = 0;
let buf = Buffer.alloc(13);
//TC
offset = buf.writeUInt32BE(this.#TC++, offset);
if (this.#TC > 0xffffffff) {
this.#TC = 0;
}
//CRC16
offset = buf.writeUInt16BE(0, offset); //sp�ter
//RC
this.#RC = this.#TC_rec + 1;
if (this.#RC > 0xffffffff) {
this.#RC = 0;
}
offset = buf.writeUInt32BE(this.#RC, offset);
//IK
offset = buf.writeUInt8(5, offset);
//PK
offset = buf.writeUInt8(1, offset);
//L
buf.writeUInt8(0, offset);
buf = this.#Telegramm_Zusatz(buf);
this.#logging(`out IK5: ${buf.toString('hex')}`, 'silly');
this.#send(buf);
}
#sendIK6() {
let offset = 0;
let buf = Buffer.alloc(13);
//TC
offset = buf.writeUInt32BE(this.#TC++, offset);
if (this.#TC > 0xffffffff) {
this.#TC = 0;
}
//CRC16
offset = buf.writeUInt16BE(0, offset); //sp�ter
//RC
this.#RC = this.#TC_rec + 1;
if (this.#RC > 0xffffffff) {
this.#RC = 0;
}
offset = buf.writeUInt32BE(this.#RC, offset);
//IK
offset = buf.writeUInt8(6, offset);
//PK
offset = buf.writeUInt8(1, offset);
//L
buf.writeUInt8(0, offset);
buf = this.#Telegramm_Zusatz(buf);
this.#logging(`out IK6: ${buf.toString('hex')}`, 'silly');
this.#send(buf);
}
#sendeMeldung() {
let offset = 0;
let buf = Buffer.alloc(12);
//TC
offset = buf.writeUInt32BE(this.#TC++, offset);
if (this.#TC > 0xffffffff) {
this.#TC = 0;
}
//CRC16
offset = buf.writeUInt16BE(0, offset); //sp�ter
//RC
this.#RC = this.#TC_rec + 1;
if (this.#RC > 0xffffffff) {
this.#RC = 0;
}
offset = buf.writeUInt32BE(this.#RC, offset);
//IK
offset = buf.writeUInt8(4, offset);
//PK
buf.writeUInt8(1, offset);
buf = Buffer.concat([buf, this.#VdS2465_Zusammenstellen()]);
buf = this.#Telegramm_Zusatz(buf);
this.#logging(`out IK4: ${buf.toString('hex')}`, 'silly');
this.#send(buf);
}
/**
* Gibt den Satz41 (TestmeldungQuittung) zurueck
*
* @returns {Buffer} Als Byte Array
*/
#getTestmeldungQuittungBuffer() {
const buf = Buffer.alloc(2);
buf.writeUInt8(0, 0);
buf.writeUInt8(0x41, 1);
return Buffer.concat([buf, this.#getDatumUhrzeitBuffer(null)]);
}
/**
* Gibt den Satz50 (Datum und Uhrzeit) zurueck
*
* @param {string | null} date Datum und Uhrzeit
* @returns {Buffer} Als Byte Array
*/
#getDatumUhrzeitBuffer(date) {
let datum;
if (typeof date === 'string') {
datum = new Date(date);
} else {
datum = new Date();
}
//log('Datum: ' + datum.toUTCString());
const Jahr = datum.getFullYear();
const Monat = datum.getMonth() + 1;
const Tag = datum.getDate();
const Stunde = datum.getHours();
const Minute = datum.getMinutes();
const Sekunde = datum.getSeconds();
let offset = 0;
const buf = Buffer.alloc(9);
buf.writeUInt8(7, offset++);
buf.writeUInt8(0x50, offset++);
buf.writeUInt8(Jahr % 100, offset++);
buf.writeUInt8(Jahr / 100, offset++);
buf.writeUInt8(Monat, offset++);
buf.writeUInt8(Tag, offset++);
buf.writeUInt8(Stunde, offset++);
buf.writeUInt8(Minute, offset++);
buf.writeUInt8(Sekunde, offset++);
//log('Datum als buffer: ' + buf.toString('hex'), 'debug');
return buf;
}
/**
* Gibt Identnummer zurueck
*
* @param {number} SL Satzlaenge
* @param {Buffer} data Datensatz
* @returns {string} Identnummernstring
*/
#getIdentnummer(SL, data) {
let Id = '',
temp;
if (SL === 0 || data.length !== SL) {
return Id;
}
for (let pos = 0; pos < SL; pos++) {
temp = data.readUInt8(pos);
if ((temp & 0x0f) == 15) {
//Identnummer fehlerhaft mit 0xFF
break;
}
temp = temp & 0x0f;
if (temp != 15) {
Id += temp.toString(10);
}
temp = data.readUInt8(pos);
temp = (temp >> 4) & 0x0f;
if (temp != 15) {
Id += temp.toString(10);
}
}
return Id;
}
//SatzFF keine stehende Verbindung
#getTrennenBuffer() {
if (!this.#Stehend) {
const buf = Buffer.alloc(2);
buf.writeUInt8(0xff, 1);
return buf;
}
const buf = Buffer.alloc(0);
return buf;
}
//Buffer ohne KeyNr und Laenge zum Checken �bergeben
#checkCRC16(data) {
let crc = 0;
let pos;
let temp;
let orginal = 0;
const len = data.length;
for (pos = 0; pos < len; pos += 2) {
temp = data.readUInt8(pos);
temp = temp << 8;
if (pos + 1 == len) {
temp = temp | 0x00;
} else {
temp = temp | data.readUInt8(pos + 1);
}
if (pos == 4) {
//�bermittelter CRC16 Wert
orginal = temp;
} else {
crc += temp;
}
if (crc > 65535) {
crc &= 0xffff;
crc++;
}
}
crc = ~crc;
crc = crc & 0xffff;
if (crc == orginal) {
return true;
}
this.#logging(`CRC16 Fehler --> Orginal=${orginal.toString(16)} Ausgerechnet=${crc.toString(16)}`, 'error');
return false;
}
//Buffer komplett �bergeben
#setCRC16buffer(data) {
let crc = 0;
let pos;
let temp;
const len = data.length;
for (pos = 0; pos < len; pos += 2) {
temp = data.readUInt8(pos);
temp = temp << 8;
if (pos + 1 == len) {
temp = temp | 0x00;
} else {
temp = temp | data.readUInt8(pos + 1);
}
if (pos == 4) {
continue;
}
crc += temp;
if (crc > 65535) {
crc &= 0xffff;
crc++;
}
}
crc = ~crc;
crc = crc & 0xffff;
data.writeUInt16BE(crc, 4);
return data;
}
//Testet UInt32 auf gleiche Werte
#isEqual(a, b) {
if (!isNaN(a) && !isNaN(b)) {
if ((a & 0xffffffff).toString(16) === (b & 0xffffffff).toString(16)) {
return true;
}
}
return false;
}
#getKopf1Buffer(SL = 0) {
const buf = Buffer.alloc(4);
//KeyNr
if (this.#UnbekannterKey) {
buf.writeUInt16BE(this.#KeyNr, 0xffff);
} else {
buf.writeUInt16BE(this.#KeyNr_rec, 0);
}
//SL
buf.writeUInt16BE(SL, 2);
return buf;
}
//verschl�sseln
#Verschluesseln(data) {
if (data.length % 16) {
data = this.#LaengeAnpassen(data);
}
this.#logging(`out -> unverschluesselte Daten ohne Key und SL: ${data.toString('hex')}`, 'silly');
const iv = Buffer.alloc(16, 0); // Initialization vector.
const cipher = crypto.createCipheriv(this.#Algorithm, Buffer.from(this.#Key, 'hex'), iv);
const encrypted = cipher.update(data, 'hex');
cipher.final();
return encrypted;
}
//entschluesseln
#Entschluesseln(data) {
const iv = Buffer.alloc(16, 0); // Initialization vector.
const decipher = crypto.createDecipheriv(this.#Algorithm, Buffer.from(this.#Key, 'hex'), iv);
decipher.setAutoPadding(false);
let decrypted = decipher.update(data, 'hex');
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted;
}
//Laenge anpassen auf 16 teilbar
#LaengeAnpassen(daten) {
if (Buffer.isBuffer(daten)) {
let diff = 16 - (daten.length % 16);
if (daten.length + diff < this.#MinLaenge) {
diff = this.#MinLaenge - daten.length;
}
if (diff > 0) {
const buf = Buffer.alloc(diff);
return Buffer.concat([daten, buf]);
}
}
return daten;
}
//Laenge , Pruefsumme und Verschluesselung
#Telegramm_Zusatz(buf) {
if (this.#KeyNr_rec > 0 && !this.#UnbekannterKey) {
//verschl�sseln
buf = this.#LaengeAnpassen(buf);
buf = this.#setCRC16buffer(buf);
buf = this.#Verschluesseln(buf);
} else {
buf = this.#setCRC16buffer(buf);
}
const SL = buf.length;
buf = Buffer.concat([this.#getKopf1Buffer(SL), buf]);
return buf;
}
//Telegrammzaehler ueberpruefen
#pruefeTelegrammZaehler() {
//this.#logging(`RC_rec=${this.#RC_rec} TC=${this.#TC}`, 'debug');
if (this.#isEqual(this.#RC_rec, this.#TC)) {
//neue Meldung
return true;
}
return false;
/*
if (this.#isEqual(this.#RC_rec, this.#TC)) {
if (this.#SendeZaehler > 3) {
this.#logging('3 Wiederholungen', 'warn');
this.#controller(_action.Disconnect);
return false;
}
this.#logging('Datensatz wiederholen (TelegrammZaehler)', 'warn');
this.#controller(_action.Wiederholung);
return false;
}
else if (this.#isEqual(this.#RC_rec, this.#TC + 1)) { //neue Meldung
return true;
} else
{
this.#logging('TelegrammZaehler falsch', 'warn');
this.#controller(_action.Disconnect);
return false;
}
*/
}
/**
* Zerlegt die Daten in VdS2465-Satz-Typen
*
* @param {Buffer} data Nutzdaten
* @returns {object} VdS-Objekt mit allen Satztypen
*/
#VdS2465_Zerlegen(data) {
let offset = 0;
const vds = {};
let sl,
typ = 0,
Satz,
Quitt = null;
while (offset < data.length) {
sl = data.readUInt8(offset++);
typ = data.readUInt8(offset++);
Satz = `Satz_${typ.toString(16)}`;
vds[Satz] = {};
vds[Satz].RawData = data.toString('hex', offset - 2, offset + sl);
switch (typ) {
case 0x01: //Priorit�t
vds[Satz].Typ = 'Prioritaet';
if (sl === 1) {
vds[Satz].Value = data.toString('hex', offset, offset + sl);
offset += sl;
}
break;
case 0x02: //Meldung
Quitt = Buffer.alloc(sl + 2);
data.copy(Quitt, 0, offset - 2, offset + sl);
Quitt.writeUInt8(0x03, 1); //Satztyp von 2 auf 3
this.#SendeSpeicher.unshift(Quitt);
// falls through
case 0x03: //Quittung auf Meldung
case 0x04: //Meldung ohne Quittung
case 0x20: //Status
switch (data.readUInt8(offset - 1)) {
case 0x02:
vds[Satz].Typ = 'Meldung Zustandsaenderung';
break;
case 0x03:
vds[Satz].Typ = 'Quittierungsruecksendung';
break;
case 0x04:
vds[Satz].Typ = 'Meldung Zustandsaenderung ohne Quittungsanforderung';
break;
case 0x20:
vds[Satz].Typ = 'Status';
break;
}
vds[Satz].Geraet = (data.readUInt8(offset) >> 4) & 0x0f;
vds[Satz].Bereich = data.readUInt8(offset++) & 0x0f;
vds[Satz].Adresse = data.readUInt8(offset++);
vds[Satz].Adresszusatz = data.readUInt8(offset++);
if (sl === 5) {
switch (data.readUInt8(offset++)) {
case 1:
vds[Satz].Adresserweiterung = 'Eingang';
break;
case 2:
vds[Satz].Adresserweiterung = 'Ausgang';
break;
case 0x10:
vds[Satz].Adresserweiterung = 'Stoerung';
switch (vds[Satz].Adresse) {
case 0x01:
vds[Satz].Zusatz = 'Unterspannung';
break;
case 0x02:
vds[Satz].Zusatz = 'Akkufehler';
break;
case 0x03:
vds[Satz].Zusatz = 'Netzfehler';
break;
case 0x17:
vds[Satz].Zusatz = 'Fehler Uebertragungs-Primaerweg';
break;
case 0x18:
vds[Satz].Zusatz = 'Fehler Uebertragungs-Ersatzweg';
break;
}
break;
}
} else {
vds[Satz].Adresserweiterung = 'Eingang';
}
vds[Satz].Meldungsart = data.readUInt8(offset++);
break;
case 0x10: //Abfrage
//this.#decodiereSatz10(sl, data.slice(offset, offset + sl));
offset += sl;
break;
case 0x11: //Fehler
vds[Satz].Typ = 'Fehler';
vds[Satz].Geraet = (data.readUInt8(offset++) >> 4) & 0x0f;
switch (data.readUInt8(offset++)) {
case 0:
vds[Satz].Value = 'Allgemein: Fehler';
break;
case 1:
vds[Satz].Value = 'Nicht bekannt';
break;
case 2:
vds[Satz].Value = 'Nicht erfuellbar';
break;
case 3:
vds[Satz].Value = 'Negativquittierung';
break;
case 4:
vds[Satz].Value = 'Falscher Sicherheitscode';
break;
case 0x10:
vds[Satz].Value = 'Adresse nicht vorhanden, ausserhalb des Bereichs';
break;
case 0x18:
vds[Satz].Value = 'Funktion ist bei dieser Adresse nicht moeglich';
break;
case 0x20:
vds[Satz].Value = 'Daten ausserhalb des Wertebereichs';
break;
case 0x80:
vds[Satz].Value = 'Pruefsumme ist fehlerhaft';
break;
case 0xff:
vds[Satz].Value = 'Satztyp unbekannt:';
vds[Satz].Value += data.readUInt8(offset++).toString(16);
break;
}
break;
case 0x24:
vds[Satz].Typ = 'Blockstatus';
offset += sl;
break;
case 0x26: //Blockstatus Alles
vds[Satz].Typ = 'Blockstatus Alles';
offset += sl;
//let test = new Buffer([0x05,0x2,0,10,0,2,0x00]);
//this.#SendeSpeicher.push(test);
break;
case 0x40: //Testmeldung
vds[Satz].Typ = 'Testmeldung';
offset += sl;
this.#SendeSpeicher.unshift(this.#getTestmeldungQuittungBuffer());
break;
case 0x41: //Quittung auf Testmeldung
vds[Satz].Typ = 'Quittung der Testmeldung';
offset += sl;
//log('Quittung Testmeldung');
break;
case 0x50: //Datum und Uhrzeit
vds[Satz].Typ = 'Datum und Uhrzeit';
vds[Satz].Value = new Date();
vds[Satz].Value.setFullYear(data.readUInt8(offset) + data.readUInt8(offset + 1) * 100);
offset += 2;
vds[Satz].Value.setMonth(data.readUInt8(offset++) - 1);
vds[Satz].Value.setDate(data.readUInt8(offset++));
if (sl === 6) {
vds[Satz].Value.setHours(data.readUInt8(offset), data.readUInt8(offset + 1), 0, 0);
offset += 2;
} else {
vds[Satz].Value.setHours(
data.readUInt8(offset),
data.readUInt8(offset + 1),
data.readUInt8(offset + 2),
0,
);
offset += 3;
}
break;
case 0x51: //Herstelleridentifikation
vds[Satz].Typ = 'Herstelleridentifikation';
vds[Satz].Value = data.toString('latin1', offset, offset + sl);
offset += sl;
break;
case 0x54: //Zeichenfolge
vds[Satz].Typ = 'Zeichenfolge';
vds[Satz].Value = data.toString('latin1', offset, offset + sl);
offset += sl;
break;
case 0x55: //aktuell unterst�tzte Satztypen
vds[Satz].Typ = 'aktuell unterstuetzte Satztypen';
offset += sl;
break;
case 0x56: //Identnummer
vds[Satz].Typ = 'Identnummer';
vds[Satz].Value = this.#getIdentnummer(sl, data.slice(offset, offset + sl));
if (!this.#Id) {
this.#Id = parseInt(vds[Satz].Value);
this.emit('connect', { id: this.Identnummer, address: this.#AddressPort });
const obj = this.#GetDevice(this.Identnummer, null);
if (obj) {
if (this.#KeyNr_rec != obj.keynr) {
this.#logging(
`Verbindung bei ${vds[Satz].Value} mit Schluesselnr:${this.#KeyNr_rec}, erwartet:${obj.keynr}`,
'warn',
);
}
this.#IdentnummerPruefen(this.Identnummer);
} else {
this.#logging(`Unbekannte Identnummer: ${this.Identnummer}`, 'warn');
}
}
offset += sl;
break;
case 0x59: {
//Ger�temerkmale
vds[Satz].Typ = 'Geraetemerkmale';
const maxIndex = offset + sl;
vds[Satz].Geraet = data.readUInt8(offset++);
let laenge, typ, index, text;
do {
text = '';
laenge = data.readUInt8(offset++);
typ = data.readUInt8(offset++);
index = data.readUInt8(offset++);
switch (typ) {
case 0:
text = 'MAC-';
break;
case 1:
text = 'IMEI-';
break;
case 2:
text = 'SIM-Kartennummer-';
break;
case 3:
text = 'Rufnummer-';
break;
case 0xff:
text = 'herstellerspezifisch-';
break;
}
switch (index) {
case 1:
text += 'Erstweg';
break;
case 2:
text += 'Zweitweg';
break;
}
text += ':';
text += data.toString('latin1', offset, offset + laenge - 2);
offset += laenge - 2;
} while (offset < maxIndex);
vds[Satz].Value = text;
break;
}
case 0x61: //Transportdienstkennung
vds[Satz].Typ = 'Transportdienstkennung';
switch (data.readUInt8(offset++)) {
case 0x10:
vds[Satz].Value = 'Analoge Festverbindung';
break;
case 0x20:
vds[Satz].Value = 'Analoge Bedarfsgesteuerte Verbindung';
break;
case 0x30:
vds[Satz].Value = 'X.25 bzw. Datex-P';
break;
case 0x40:
vds[Satz].Value = 'ISDN, B-Kanal';
break;
case 0x50:
vds[Satz].Value = 'ISDN, D-Kanal';
break;
case 0x60:
vds[Satz].Value = 'Buendelfunk, Betriebsfunk';
break;
case 0x70:
vds[Satz].Value = 'Datenfunk';
break;
case 0x80:
vds[Satz].Value = 'Mobilfunk';
break;
case 0x90:
vds[Satz].Value = 'TCP/IP-Intranet-Uebertragung';
break;
}
break;
case 0x73:
vds[Satz].Typ = 'Telegrammzaehler';
vds[Satz].Geraet = (data.readUInt8(offset) >> 4) & 0x0f;
vds[Satz].Bereich = data.readUInt8(offset++) & 0x0f;
vds[Satz].Value = data.readUInt8(offset++);
break;
case 0xff: //Verbindung wird nicht mehr ben�tigt
vds[Satz].Typ = 'Verbindung wird nicht mehr benoetigt';
offset += sl;
break;
default:
offset += sl;
}
}
return vds;
}
/**
* Gibt die VdS Nutzdaten zurueck, inclusive das Laengenbyte
*
* @returns {Buffer} Als Byte-Array
*/
#VdS2465_Zusammenstellen() {
let buf_msg;
buf_msg = this.#SendeSpeicher.shift();
if (!this.#Stehend && this.#SendeSpeicher.length === 0) {
buf_msg = Buffer.concat([buf_msg, this.#getTrennenBuffer()]);
}
const buf = Buffer.alloc(1);
//L
buf.writeUInt8(buf_msg.length, 0);
return Buffer.concat([buf, buf_msg]);
}
//Empfangene Daten -> Laenge Pr�fen
#LaengePruefen(data) {
if (data.length < 17) {
this.#logging(`Datensatz zu kurz (${data.length.toString()})!`, 'warn');
return false;
}
const sl = data.readUInt16BE(2);
if (data.length < sl + 4) {
return false;
}
return true;
}
/**
* Gibt Geraete-Datensatz zurueck
*
* @param {string | null} Id Identnummer
* @param {number | null} KeyNr Keynummer
* @returns {object} Datensatz
*/
#GetDevice(Id, KeyNr) {
let device = null;
if (this.#KeyList.length > 0) {
for (let i = 0; i < this.#KeyList.length; i++) {
if (Id) {
if (this.#KeyList[i].identnr === Id) {
device = this.#KeyList[i];
break;
}
} else if (KeyNr) {
if (this.#KeyList[i].keynr === KeyNr) {
device = this.#KeyList[i];
break;
}
}
}
}
return device;
}
#SchluesselPruefen(data) {
this.#KeyNr_rec = data.readUInt16BE(0);
if (this.#KeyNr_rec === 0) {
return true;
}
const obj = this.#GetDevice(null, this.#KeyNr_rec);
if (obj) {
this.#KeyNr = this.#KeyNr_rec;
this.#Key = obj.key;
this.#Stehend = obj.stehend;
this.#UnbekannterKey = false;
return true;
}
this.#UnbekannterKey = true;
return false;
}
//Prueft die empfangene Identnummer mit der hinterlegten Identnummer
#IdentnummerPruefen(id) {
const obj = this.#GetDevice(id, null);
if (obj) {
this.#Stehend = obj.stehend;
return true;
}
return false;
}
#Auswertung(data) {
let offset = 2;
this.#KeyNr_rec = data.readUInt16BE(0);
this.#SL = data.readUInt16BE(offset);
const buf = data.slice(4); //Kopf1 entfernen
if (this.#KeyNr_rec > 0) {
data = this.#Entschluesseln(buf);
this.#logging(`Antwort entschluesselt: ${data.toString('hex')}`, 'silly');
} else {
data = buf;
}
offset = 0;
if (this.#checkCRC16(data) === false) {
this.#controller(_action.Checksummenfehler);
return false;
}
this.#TC_rec = data.readUInt32BE(offset);
offset += 4;
offset += 2; //CRC16
this.#RC_rec = data.readUInt32BE(offset);
offset += 4;
this.#IK = data.readUInt8(offset++);
this.#PK = data.readUInt8(offset++);
this.#L = data.readUInt8(offset++);
if (13 + this.#L > this.#SL) {
this.#logging('Satzlaenge kleiner als Nutzdaten', 'warn');
this.#controller(_action.Disconnect);
return false;
}
if (this.#PK !== 1) {
this.#logging('Protokoll nicht VdS2465 (PK)', 'warn');
this.#controller(_action.IK6);
return false;
}
if (this.#IK !== 1 && this.#IK !== 2 && this.#IK !== 7) {
if (!this.#pruefeTelegrammZaehler()) {
//this.#controller(_action.Disconnect);
return false;
}
}
this.#SendeZaehler = 0;
switch (this.#IK) {
case 1:
return true;
//break;
case 2:
this.#controller(_action.IK3);
return true;
//break;
case 3:
if (this.#SendeSpeicher.length > 0) {
this.#controller(_action.IK4);
} else {
this.#controller(_action.IK3_nach_Pollzeit);
}
return true;
//break;
case 4:
this.#Inhalt = this.#VdS2465_Zerlegen(data.slice(offset, offset + this.#L));
this.#logging(JSON.stringify(this.#Inhalt), 'debug');
this.emit('data', this.#Inhalt);
if (this.#VdSRequestCounter == 1) {
this.#VdSRequestCounter++;
}
if (this.#SendeSpeicher.length > 0) {
this.#controller(_action.IK4);
} else {
this.#controller(_action.IK3_nach_Pollzeit);
}
return true;
//break;
case 5:
this.#logging('Fehlermeldung: IK war unbekannt!', 'warn');
this.#VdS2465_Zerlegen(data.slice(offset, offset + this.#L));
break;
case 6:
this.#logging('Fehlermeldung: PK war unbekannt!', 'warn');
this.#VdS2465_Zerlegen(data.slice(offset, offset + this.#L));
break;
case 7: {
//Wenn Z�hler nicht stimmen!
let i = this.#RC - 1;
if (i < 0) {
i = 0xffffffff;
}
if (this.#TC_rec !== i) {
this.#logging(
`IK7: Zaehler TC von ${this.#TC_rec.toString(16)} in ${i.toString(16)} geaendert!`,
'debug',
);
this.#TC_rec = i;
}
this.#controller(_action.VdSServiceRequest);
return true;
//break;
}
default:
this.#controller(_action.IK5);
return true;
//break;
}
return false;
}
}
module.exports = vds2465server;