/****************************************************************************
** При запуске сервера, скрипт читает списки пультов, разделов, шлейфов и т.д.,
** транслирует этот список на mqtt шину.
** События транслирует в нужные разделы шины mqtt.
** Выполняет действия по приходу событий с шины mqtt.
** Сценарий только для сервера (поместить в каталог Scripts\ScriptsServer).
** Create by Oleg Lisovskiy 2015 year
**
** Неплохой сайт для проверки работы MQTT: https://wqtt.ru
**
** Топики будут вида:
** all/bolid/pku_1/device_3/sh_1	24
** all/bolid/pku_1/device_3/sh_1/meta/desc	"Дымовой"
** all/bolid/pku_1/device_3/sh_1/adc	0
**
** Для постановки на охрану нужно опубликовать:
** all/bolid/pku_1/device_3/sh_1/on 1
** Если в сообщении будет не 1, а 0, то шлеф будет снят с охраны.
****************************************************************************/

//Параметры подключения к брокеру MQTT
var MQTT_SERVER   = "localhost";
var MQTT_PORT     = 1883;
var MQTT_CLIENTID = "clientId";
var MQTT_USER     = "user";
var MQTT_PASSWD   = "password";
var MQTT_ROOT     = "all/bolid";

var PREFIX_PKU    = "pku_";
var PREFIX_DEV    = "device_";
var PREFIX_SH     = "sh_";
var PREFIX_RELAY  = "relay_";
var PREFIX_PART   = "part_";

//---------------------------------------------------------------------------
// Запрашивать АЦП указанных шлейфов:
// Раскомментируйте и отредактируйте данный блок
/*
var ADC_REQ = [
            {
                "pku": "1",
                "devs": [
                    {
                        "dev": "4",
                        "sh": [ 1,2,3 ],
                    },
                ],
            },
        ];
*/

//---------------------------------------------------------------------------
// Запрашивать счетчики (С2000-АСРх) указанных шлейфов:
// Раскомментируйте и отредактируйте данный блок
/*
var COUNTER_REQ = [
            {
                "pku": "1",
                "devs": [
                    {
                        "dev": "9",
                        "sh": [ 126,127 ],
                    },
                ],
            },
        ];
*/
//===========================================================================
armSkif.writeLog( "Start script: " + scriptFileName );

armSkif.signalPollFinished.connect      ( sig_pollFinished );
armSkif.signalUpdateDeviceState.connect ( sig_UpdateDeviceState );
armSkif.signalUpdateSh.connect          ( sig_UpdateSh );
armSkif.signalUpdateExit.connect        ( sig_UpdateRelay );
armSkif.signalUpdateRelay.connect       ( sig_UpdateExit );
armSkif.signalUpdatePart.connect        ( sig_UpdatePart );
armSkif.signalUpdateGPart.connect       ( sig_UpdateGPart );
armSkif.signalUpdateADC.connect         ( sig_UpdateADC );
armSkif.signalUpdateCount.connect       ( sig_UpdateCount );

armSkif.setInterval(function() { requestADC(ADC_REQ); }, 60000);
armSkif.setInterval(function() { requestCount(COUNTER_REQ); }, 60000);

var timerReconnect = 0; // Таймер попдключения в случае ошибки или разрыва соединения
var reconnect = false;  // Определяем что произошло переподключение. Для инициализации состояний.

mqtt.init                   ( MQTT_SERVER, MQTT_PORT, MQTT_CLIENTID, MQTT_USER, MQTT_PASSWD );
mqtt.connected.connect      ( mqtt_connected );
mqtt.received.connect       ( mqtt_received );
mqtt.error.connect          ( mqtt_error );
mqtt.disconnected.connect   ( mqtt_disconnected );

mqtt.connect(); // Подключиться к брокеру.

//===========================================================================
// MQTT
//---------------------------------------------------------------------------
// Функция вызывается при подтверждении установления соединения с брокером.
function mqtt_connected()
{
    armSkif.writeLog( scriptFileName + " (mqtt_connected)");
    mqtt.subscribe(MQTT_ROOT + "/+/+/+/on");
    mqtt.subscribe(MQTT_ROOT + "/+/+/on");

    if (reconnect) {
        reconnect = false;

        var listPKU = armSkif.getListPKU();  // Список пультов
        for (var i=0; i<listPKU.length; ++i) // Цикл для каждого пульта
        {
            var numPKU = listPKU[i]; // Номер пульта
            sig_pollFinished(numPKU);
        }
    }
}
//---------------------------------------------------------------------------
//Функция вызывается при получении сообщении от брокера.
function mqtt_received(topic, payload)
{
    armSkif.writeLog( scriptFileName + " (mqtt_received): " + topic +": "+ payload);

    var obj={};

    var str = "^" + MQTT_ROOT + "\\/" + PREFIX_PKU + "(\\d+)\\/" + PREFIX_PART + "(\\d+)\\/on";
    var myReg = new RegExp(str);
    if ( myReg.test(topic) ) {
        obj.state = "part";
        var Arr   = myReg.exec(topic);
        obj.pku   = Arr[1];
        obj.part  = Arr[2];
        obj.value = payload;
    }
    else {
        str = "^" + MQTT_ROOT + "\\/" + PREFIX_PKU + "(\\d+)\\/" + PREFIX_DEV + "(\\d+)\\/"+ PREFIX_SH + "(\\d+)\\/on";
        myReg = new RegExp(str);
        if ( myReg.test(topic) ) {
            obj.state = "sh";
            var Arr   = myReg.exec(topic);
            obj.pku   = Arr[1];
            obj.dev   = Arr[2];
            obj.sh    = Arr[3];
            obj.value = payload;
        }
        else{
            str = "^" + MQTT_ROOT + "\\/" + PREFIX_PKU + "(\\d+)\\/" + PREFIX_DEV + "(\\d+)\\/"+ PREFIX_RELAY + "(\\d+)\\/on";
            myReg = new RegExp(str);
            if ( myReg.test(topic) ) {
                obj.state = "relay";
                var Arr   = myReg.exec(topic);
                obj.pku   = Arr[1];
                obj.dev   = Arr[2];
                obj.relay = Arr[3];
                obj.value = payload;
            }
        }
    }

    if (obj.state) skif_command(obj);
}
//---------------------------------------------------------------------------
// Функция вызывается при возникновении ошибки в процессе соединения.
function mqtt_error(err)
{
    armSkif.writeLog( scriptFileName + " (mqtt_error): " + err);
    mqtt.disconnect(); // Отключиться от брокера.
    mqtt_timeReconnect();
}
//---------------------------------------------------------------------------
// Функция вызывается при отключении от брокера.
function mqtt_disconnected()
{
    armSkif.writeLog( scriptFileName + " (mqtt_disconnected)");
    mqtt_timeReconnect();
}
//---------------------------------------------------------------------------
function mqtt_timeReconnect()
{
    // Через одну секунду вызвать функцию mqtt_reconnect
    if (timerReconnect==0)
        timerReconnect = armSkif.setTimeout(mqtt_reconnect, 1000);
}
//---------------------------------------------------------------------------
function mqtt_reconnect()
{
    timerReconnect = 0;
    reconnect = true;
    mqtt.connect();    // Подключиться к брокеру.
}

//===========================================================================
// SKIF
//---------------------------------------------------------------------------
function sig_pollFinished(nPKU)
{
    armSkif.writeLog( scriptFileName + " (Start PollFinished)");

    mqtt_make_pku_meta( nPKU,armSkif.getPKUTypeStr    (nPKU), "type" );
    mqtt_make_pku_meta( nPKU,armSkif.getPKUVersion    (nPKU), "ver"  );
    mqtt_make_pku_meta( nPKU,armSkif.getPKUDescription(nPKU), "desc" );

    var listDevices = armSkif.getListDevices(nPKU); // Список приборов
    for (var j=0; j<listDevices.length; ++j) // Цикл для каждого прибора
    {
        var dev = listDevices[j]; // Номер прибора
        mqtt_make_device_meta( nPKU, dev, armSkif.getDeviceTypeStr    (nPKU, dev), "type" );
        mqtt_make_device_meta( nPKU, dev, armSkif.getDeviceVersion    (nPKU, dev), "ver" );
        mqtt_make_device_meta( nPKU, dev, armSkif.getDeviceDescription(nPKU, dev), "desc" );
        mqtt_make_device_meta( nPKU, dev, armSkif.getDeviceTamperState(nPKU, dev), "ts" );
        mqtt_make_device_meta( nPKU, dev, armSkif.getDevicePowerState (nPKU, dev), "ps" );
        mqtt_make_device_meta( nPKU, dev, armSkif.getDeviceTamperState(nPKU, dev) );

        var listSh = armSkif.getListSh(nPKU, dev); // Список шлейфов
        for (var k=0; k<listSh.length; ++k) // Цикл для каждого шлейфа
        {
            var sh = listSh[k]; // Номер шлейфа
            mqtt_make_sh_meta( nPKU, dev, sh, armSkif.getShDescription(nPKU, dev, sh), "desc" );
            mqtt_make_sh_meta( nPKU, dev, sh, armSkif.getShState      (nPKU, dev, sh) );
        }

        var listRelay = armSkif.getListRelay(nPKU, dev); // Список реле
        for (var k=0; k<listRelay.length; ++k) // Цикл для каждого реле
        {
            var relay = listRelay[k]; // Номер реле
            mqtt_make_relay_meta( nPKU, dev, relay, armSkif.getRelayDescription(nPKU, dev, relay), "desc" );
            mqtt_make_relay_meta( nPKU, dev, relay, armSkif.getRelayState      (nPKU, dev, relay) );
        }

        var listExit = armSkif.getListExit(nPKU, dev); // Список контролируемых цепей реле
        for (var k=0; k<listExit.length; ++k) // Цикл для каждой контролируемой цепи реле
        {
            var relay = listExit[k]; // Номер контролируемой цепи реле
            mqtt_make_relay_meta( nPKU,dev,relay,armSkif.getRelayDescription(nPKU, dev, relay),"desc" );
            mqtt_make_relay_meta( nPKU,dev,relay,armSkif.getExitState       (nPKU, dev, relay),"state" );
        }

    }
    var listParts = armSkif.getListParts(nPKU); // Список разделов
    for (var j=0; j<listParts.length; ++j) // Цикл для каждого раздела
    {
        var part = listParts[j]; // Номер раздела
        mqtt_make_part_meta( nPKU, part,armSkif.getPartDescription(nPKU, part),"desc" );
        mqtt_make_part_meta( nPKU, part,armSkif.getPartState      (nPKU, part) );
    }
    var listGParts = armSkif.getListGParts(nPKU); // Список групп разделов
    for (var j=0; j<listGParts.length; ++j) // Цикл для каждой группы разделов
    {
        var part = listGParts[j]; // Номер группы разделов
        mqtt_make_part_meta( nPKU, part, armSkif.getPartDescription(nPKU, part),"desc" );
        mqtt_make_part_meta( nPKU, part, armSkif.getPartState      (nPKU, part) );
    }

    //armSkif.writeLog( scriptFileName + " (REQUEST ADC)");
    requestADC(ADC_REQ);

    //armSkif.writeLog( scriptFileName + " (REQUEST COUNT)");
    requestCount(COUNTER_REQ_REQ);

    armSkif.writeLog( scriptFileName + " (End PollFinished)");
}
//---------------------------------------------------------------------------
function requestADC(obj_adc)
{
    //armSkif.writeLog( scriptFileName + " ADC REQUEST ");

    // Запросить АЦП шлейфов. Ответ будет в signalUpdateADC.
    for (var j=0; j<obj_adc.length; ++j)
    {
        var adc_pku = obj_adc[j].pku;
        var adc_devs =obj_adc[j].devs;
        for ( var k=0; k<adc_devs.length; ++k){
            var adc_dev=adc_devs[k].dev;
            var adc_shs=adc_devs[k].sh;
            for ( var m=0; m<adc_shs.length; ++m){
                var adc_sh=adc_shs[m];
                //armSkif.writeLog( scriptFileName + " ADC_SH= " + adc_sh);
                armSkif.requestShADC(adc_pku,adc_dev,adc_sh);
            }
        }
    }
}
//---------------------------------------------------------------------------
function sig_UpdateADC(nPKU, fsh, adc, value)
{
    var dev = fsh >>8;
    var sh  = fsh & 0x00FF;

    var topic  = MQTT_ROOT + "/" + PREFIX_PKU + nPKU + "/" + PREFIX_DEV + dev + "/" + PREFIX_SH + sh + "/adc";
    var nValue = value;

    //if ( value == '' ) nValue=adc;
    if ( value == '' )
    {
        if (adc>127) nValue=adc-256;
        else nValue=adc;
    }

    /*
    var d = new Date();
    var stamp = d.getUnixTimestamp();
    nValue = stamp + ' ("' + nValue + ' C")';
    */

    mqtt.publish(topic, nValue, true);

    //armSkif.writeLog( scriptFileName + " ( "+ topic + " )"+ "adc= " + adc + "value= " + value + "nValue= " + nValue);
}
//---------------------------------------------------------------------------
function requestCount(obj_cnt)
{
    //armSkif.writeLog( scriptFileName + " COUNTER REQUEST ");

    // Запросить счетчик С2000-АСРх. Ответ будет в signalUpdateCount.
    for (var j=0; j<obj_cnt.length; ++j)
    {
        var cnt_pku = obj_cnt[j].pku;
        var cnt_devs =obj_cnt[j].devs;
        for ( var k=0; k<cnt_devs.length; ++k){
            var cnt_dev=cnt_devs[k].dev;
            var cnt_shs=cnt_devs[k].sh;
            for ( var m=0; m<cnt_shs.length; ++m){
                var cnt_sh=cnt_shs[m];
                //armSkif.writeLog( scriptFileName + " Counter_SH= " + cnt_sh);
                armSkif.requestCount(cnt_pku,cnt_dev,cnt_sh);
            }
        }
    }
}
//---------------------------------------------------------------------------
function sig_UpdateCount(nPKU, fsh, val)
{
    var dev = fsh >>8;
    var sh  = fsh & 0x00FF;

    var topic  = MQTT_ROOT + "/" + PREFIX_PKU + nPKU + "/" + PREFIX_DEV + dev + "/" + PREFIX_SH + sh + "/counter";

    mqtt.publish(topic, val, true);

    //armSkif.writeLog( scriptFileName + " ( "+ topic + " )"+ "counter= " + val);
}
//---------------------------------------------------------------------------
function sig_UpdateDeviceState(nPKU, dev)
{
    if ( dev == 0 ){
        var listDevices = armSkif.getListDevices(nPKU);
        for (var j=0; j<listDevices.length; ++j)
        {
            var ndev=listDevices[j];
            mqtt_make_device_meta( nPKU,ndev, armSkif.getDeviceTamperState(nPKU, ndev),"ts" );
            mqtt_make_device_meta( nPKU,ndev, armSkif.getDevicePowerState (nPKU, ndev),"ps" );
        }
    }
    else{
        mqtt_make_device_meta( nPKU, dev, armSkif.getDeviceTamperState(nPKU, dev),"ts" );
        mqtt_make_device_meta( nPKU, dev, armSkif.getDevicePowerState (nPKU, dev),"ps" );
    }
}
//---------------------------------------------------------------------------
function sig_UpdateSh(nPKU, fsh)
{
    if ( fsh == 0 ){
        var listDevices = armSkif.getListDevices(nPKU);
        for (var j=0; j<listDevices.length; ++j)
        {
            var ndev=listDevices[j];
            var listSh = armSkif.getListSh(nPKU, ndev); // Список шлейфов
            for (var k=0; k<listSh.length; ++k) // Цикл для каждого шлейфа
            {
                var sh = listSh[k]; // Номер шлейфа
                mqtt_make_sh_meta( nPKU, ndev, sh, armSkif.getShState(nPKU, ndev, sh) );
            }
        }
    }
    else{
        var dev = fsh >>8;
        var sh  = fsh & 0x00FF;
        mqtt_make_sh_meta( nPKU, dev,sh, armSkif.getShState(nPKU, dev, sh) );
    }
}
//---------------------------------------------------------------------------
function sig_UpdateRelay(nPKU, frl)
{
    if ( frl == 0 ){
        var listDevices = armSkif.getListDevices(nPKU);
        for (var j=0; j<listDevices.length; ++j)
        {
            var ndev = listDevices[j];
            var listRelay = armSkif.getListRelay(nPKU, ndev); // Список реле
            for (var k=0; k<listRelay.length; ++k)
            {
                var relay = listRelay[k];
                mqtt_make_relay_meta ( nPKU, ndev, relay, armSkif.getRelayState(nPKU, ndev, relay) );
            }
        }
    }
    else{
        var dev = frl >>8;
        var relay  = frl & 0x00FF;
        mqtt_make_relay_meta (nPKU, dev, relay, armSkif.getRelayState(nPKU, dev, relay) );
    }
}
//---------------------------------------------------------------------------
function sig_UpdateExit(nPKU, frl)
{
    if ( frl == 0 ){
        var listDevices = armSkif.getListDevices(nPKU);
        for (var j=0; j<listDevices.length; ++j)
        {
            var ndev=listDevices[j];
            var listExit = armSkif.getListExit(nPKU, ndev); // Список контролируемых цепей реле
            for (var k=0; k<listExit.length; ++k)
            {
                var relay = listExit[k];
                mqtt_make_relay_meta( nPKU,ndev,relay,armSkif.getExitState(nPKU, ndev, relay), "state" );
            }
        }
    }
    else{
        var dev = frl >>8;
        var relay  = frl & 0x00FF;
        mqtt_make_relay_meta( nPKU, dev, relay, armSkif.getRelayState(nPKU, dev, relay), "state" );
    }
}
//---------------------------------------------------------------------------
function sig_UpdatePart(nPKU, part)
{
    if ( part == 0 ){
        var listParts = armSkif.getListParts(nPKU); // Список разделов
        for (var j=0; j<listParts.length; ++j)
        {
            var npart = listParts[j];
            mqtt_make_part_meta( nPKU, npart, armSkif.getPartState(nPKU, npart) );
        }
    }
    else
        mqtt_make_part_meta( nPKU, part, armSkif.getPartState(nPKU, part) );
}
//---------------------------------------------------------------------------
function sig_UpdateGPart(nPKU, gpart)
{
    if ( gpart == 0 ){
        var listGParts = armSkif.getListGParts(nPKU); // Список групп разделов
        for (var j=0; j<listGParts.length; ++j)
        {
            var ngpart = listGParts[j];
            mqtt_make_part_meta( nPKU, ngpart, armSkif.getPartState(nPKU, ngpart) );
        }
    }
    else
        mqtt_make_part_meta( nPKU, gpart, armSkif.getPartState(nPKU, gpart) );
}
//---------------------------------------------------------------------------
function skif_command(obj)
{
    //armSkif.writeLog( scriptFileName + "(skif_command_START)" + obj.state);

    var re = new RegExp("\[01\]");
    if (! re.test(obj.value) ) {
        //armSkif.writeLog( scriptFileName + "(Command not 0 or 1)");
        return;
    }
    if (obj.state == "relay"){
        //armSkif.writeLog( scriptFileName + "(Command Relay)");
        var prog = (obj.value == 1) ? armSkif.RL_ON : armSkif.RL_OFF;
        armSkif.controlRelay(obj.pku, obj.dev, obj.relay, prog);

    }
    if (obj.state == "sh"){
        //armSkif.writeLog( scriptFileName + "(Command Sh)");
        if ( obj.value == 0 )
            armSkif.controlSh_DisArm(obj.pku, obj.dev, obj.sh);
        else
            armSkif.controlSh_Arm(obj.pku, obj.dev, obj.sh);
    }
    if (obj.state == "part"){
        //armSkif.writeLog( scriptFileName + "(Command Part)");
        if ( obj.value == 0 )
            armSkif.controlPart_DisArm(obj.pku,obj.part);
        else
            armSkif.controlPart_Arm(obj.pku,obj.part);
    }
}

//===========================================================================
// META
//---------------------------------------------------------------------------
function mqtt_make_pku_meta(nPKU, nValue, nmeta_Name)
{
    var topic = MQTT_ROOT + "/" + PREFIX_PKU + nPKU;

    if (nmeta_Name !== undefined){
        topic += "/meta/" + nmeta_Name;

        if ( nmeta_Name == "ver" )
            nValue /= 100;
    }

    if(/\D+/.test(nValue))
        nValue='"' + nValue + '"';

    mqtt.publish(topic, nValue, true);
}
//---------------------------------------------------------------------------
function mqtt_make_device_meta(nPKU, nDev, nValue, nmeta_Name)
{
    var topic = MQTT_ROOT + "/" + PREFIX_PKU + nPKU + "/" + PREFIX_DEV + nDev;

    if (nmeta_Name !== undefined) {
        topic += "/meta/" + nmeta_Name;

        if ( nmeta_Name == "ver" )
            nValue/=100;
    }

    if(/\D+/.test(nValue))
        nValue='"' + nValue + '"';

    mqtt.publish(topic, nValue, true);
}
//---------------------------------------------------------------------------
function mqtt_make_sh_meta(nPKU, nDev, nSh, nValue, nmeta_Name)
{
    var topic = MQTT_ROOT + "/" + PREFIX_PKU + nPKU + "/" + PREFIX_DEV + nDev + "/" + PREFIX_SH + nSh;

    if (nmeta_Name !== undefined)
        topic += "/meta/" + nmeta_Name;

    if(/\D+/.test(nValue))
        nValue='"' + nValue + '"';

    mqtt.publish(topic, nValue, true);
}
//---------------------------------------------------------------------------
function mqtt_make_relay_meta(nPKU, nDev, nRelay, nValue, nmeta_Name)
{
    var topic = MQTT_ROOT + "/" + PREFIX_PKU + nPKU + "/" + PREFIX_DEV + nDev + "/" + PREFIX_RELAY + nRelay;

    if (nmeta_Name !== undefined)
        topic += "/meta/" + nmeta_Name;

    if(/\D+/.test(nValue))
        nValue='"' + nValue + '"';

    mqtt.publish(topic, nValue, true);
}
//---------------------------------------------------------------------------
function mqtt_make_part_meta(nPKU, nPart, nValue, nmeta_Name)
{
    var topic = MQTT_ROOT + "/" + PREFIX_PKU + nPKU + "/" + PREFIX_PART + nPart;

    if (nmeta_Name !== undefined)
        topic += "/meta/" + nmeta_Name;

    if(/\D+/.test(nValue))
        nValue='"' + nValue + '"';

    mqtt.publish(topic, nValue, true);
}

//---------------------------------------------------------------------------
Date.prototype.getUnixTimestamp = function()
{
    return Math.round(this.getTime() / 1000);
}
