Source: plugins/inbound/katsys/katsys.js

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;