const EventEmitter = require('events');
const fs = require('fs');
var WebSocket = require('ws');
const Parser = require(__dirname + '/parsers/parser_ILSLAU');
/**
* Class for connection to the KatSys Cloud
*
* ALARMiator Zusatzalarmierung
*
* version: 1.0.0
* author: Jens Dinstühler 2020
*/
class KatsysCloud extends EventEmitter {
constructor() {
super();
// WebSocket object
this._ws = null;
this._socketUrl = 'wss://connect.katsys.cloud:81';
this._connected = false;
this._lastHeartbeat = null;
this._heartBeatInterval = null;
this._heartBeatIntervalTime = 30000;
this._heartBeatMaxTryCounter = 100; // Number of retries to reconnect to KatSys Cloud automtically.
this._heartBeatTryCounter = 0;
this._loglevel = 'prod';
this._connected = false;
// Parser
this._parser = new Parser();
this._parser.on('statusParsed', () => {
console.log('KATSYS | PARSER | statusParsed');
})
this._parser.on('statusTranslate_successful', (statusReadable) => {
this.parserReturnedStatus();
})
this._parser.on('zveiParsed', () => {
this.parserReturnedZVEI();
})
this._parser.on('alarm_parsed', () => {
this.parserReturnedAlarm();
})
}
set connected(bolConnected) {
if (this._connected != bolConnected) {
this._connected = bolConnected;
this.emit('connectionStateChanged', bolConnected);
}
}
get connected() {
return this._connected;
}
set masterToken(masterToken) {
this._masterToken = masterToken;
}
get masterToken() {
return this._masterToken;
}
set subToken(subToken) {
this._subToken = subToken;
}
set pemFile(pathToPemFile) {
this._pathToPemFile = pathToPemFile;
}
get pemFile() {
return this._pathToPemFile;
}
set socketUrl(socketUrl) {
this._socketUrl = socketUrl;
}
get socketUrl() {
return this._socketUrl;
}
set loglevel(loglevel) {
this._loglevel = loglevel;
}
get loglevel() {
return this._loglevel;
}
/**
* Log Function for inter process communication
*/
LogAtMain(msg) {
process.send('{"logmessage" : "' + msg + '"}');
}
// Called in Event of Status parser finished successfully
parserReturnedStatus() {
this.LogAtMain('KATSYS | STATUS | ' + this._parser.radioStatus + ' ' + this._parser.radioStatusHumanReadable + ' (' + this._parser.issi + ')');
var stateInfo = {
radioStatus: this._parser.radioStatus,
radioStatusHumanReadable: this._parser.radioStatusHumanReadable,
radioStatusShort: this._parser.radioStatusShort,
issi: this._parser.issi
}
this.emit('statusParsed', stateInfo);
}
parserReturnedZVEI() {
this.LogAtMain('KATSYS | ZVEI | ' + this._parser.zveicode + ' ' + ' (' + this._parser.zveidescription + ')');
var zveiInfo = {
zveiCode: this._parser.zveicode,
zveidescription: this._parser.zveidescription
}
this.emit('zveiParsed', zveiInfo);
}
parserReturnedAlarm() {
this.LogAtMain('KATSYS | ALARM');
var alarmInfo = this._parser.operation;
this.emit('alarmParsed', alarmInfo);
}
incomingDataHandler(data) {
//process.send('incoming data: ' + data);
this.LogAtMain('KATSYS | INCOMING | RAW | ' + data);
this.connected = true;
// write line to logfile
//this.log.info('RX: ', data);
this.renderMessage(data);
}
renderMessage(data) {
var dataAsJSON = JSON.parse(data);
switch (dataAsJSON.statusCode) {
case 'auth_failed':
this.emit('auth_failed', null);
break;
case 'auth_successful':
this.emit('auth_successful', null);
break;
case 'already_connected':
this.emit('already_connected', null);
break;
case 'connection_established':
this.emit('connection_established', null);
break;
case 'alarm_data':
this.emit('alarm_data', null);
this._parser.parseRAWAlarmData(dataAsJSON.data.textElements);
break;
case 'radio_status':
this.emit('radio_status', null);
this._parser.parseRAWStatusData(dataAsJSON);
break;
case 'zvei':
this.emit('zvei', null);
this._parser.parseRAWZVEIData(dataAsJSON);
default:
this.emit('unknownData', null);
break;
}
}
/**
* start heartbeat check
*/
startReconnectTimer() {
this._lastHeartbeat = new Date();
this._heartBeatInterval = setInterval(this.checkHeartbeat.bind(this), 30000);
this.LogAtMain('Starting heartbeat check each 30 seconds');
}
/**
* Checks heatbeat driven by startReconnectTimer Interva
*/
checkHeartbeat() {
var thisHeartbeatDate = new Date();
var dif = thisHeartbeatDate - this._lastHeartbeat;
var SecondsBetween = dif / 1000;
var SecondsBetween = Math.abs(SecondsBetween);
if (SecondsBetween >= 60) {
this.connected = false;
this._heartBeatTryCounter++;
console.log('KATSYS | Heartbeat missed!! Try reconnecting to KatSys Cloud | #' + this._heartBeatTryCounter);
if (this._heartBeatTryCounter > this._heartBeatMaxTryCounter) {
console.log('KATSYS | Tried ' + this._heartBeatTryCounter + ' times to reconnect to KatSys Cloud. Givibg up now!');
this.stopReconnectTimer();
} else {
this.emit('katsys_try_reconnect', this._heartBeatTryCounter);
this.reconnectConnection();
}
} else {
if (this._loglevel === 'debug') {
// (' + SecondsBetween + ' | ' + dif + ')');
this.LogAtMain('Heartbeat Check successful (' + SecondsBetween + ' | ' + dif + ')');
}
}
}
/**
* stops heartbeat check
*/
stopReconnectTimer() {
if (this._heartBeatInterval != null) {
this.LogAtMain('Stopping Heartbeat check');
clearInterval(this._heartBeatInterval);
}
}
/**
* destroys WebSocket object and starts a new connection
*/
reconnectConnection() {
this._ws.removeAllListeners();
this._ws = null;
//this.stopReconnectTimer();
try {
this.openConnection();
}
catch (exception){
console.log(exception);
this.LogAtMain('exception caught during reconnect from KatSys Cloud');
setTimeout(() => {
this.reconnectConnection();
}, this._heartBeatIntervalTime)
}
}
/**
* opens Connection to KatSys Cloud Service
*/
openConnection() {
this.LogAtMain('Initialising WebSocket');
this._ws = new WebSocket(this._socketUrl, {
headers: {
"master_token": this._masterToken,
"sub_token": this._subToken,
},
ca: [
fs.readFileSync(this._pathToPemFile)
],
});
this._ws.on('message', (data) => {
this.LogAtMain('RAW: ' + data);
this.renderMessage(data);
this.emit('data', data);
});
this._ws.on('open', () => {
this.emit('open', null);
this.stopReconnectTimer();
this.connected = true;
this._lastHeartbeat = new Date();
this._heartBeatTryCounter = 0;
this.startReconnectTimer();
this.LogAtMain('Connection to KatSys Cloud opened');
});
this._ws.on('close', function () {
this.connected = false;
this.LogAtMain('disconnected from KatSys Cloud');
this.emit('closedConnectionRemote', null);
setTimeout(() => {
this.reconnectConnection();
}, this._heartBeatIntervalTime)
}.bind(this));
this._ws.on('error', (error) => {
console.log('KATSYS | ERROR | Error message: ' + error.message);
this.connected = false;
this.emit('ws_error', error);
});
this._ws.on('ping', () => {
this.connected = true;
var thisHeartbeatDate = new Date();
var dif = thisHeartbeatDate - this._lastHeartbeat;
var SecondsBetween = dif / 1000;
var SecondsBetween = Math.abs(SecondsBetween);
this._lastHeartbeat = thisHeartbeatDate;
this.emit('ping', null);
})
}
/**
* closes WebSocket Connection to KatSys Cloud
*/
closeConnection() {
console.log('KATSYS | Closing WebSocket Connection to KatSys Cloud');
this.LogAtMain('Closing WebSocket Connection to KatSys Cloud');
this.connected = false;
this._ws.close();
this.emit('closedConnection');
}
}
module.exports = KatsysCloud;