Для обработки данных на локальном сервере есть возможность разрабатывать собственные скрипты на языке javscript. Скрипты располагаются в директории /var/lib/wliotproxyd/js_data_processing/. Каждый скрипт запускается сервером в выделенном окружении и работает независимо от других. Пользователь может, используя консольный клиент, останавливать и запускать скрипты, при этом при перезапуске сервер считывает код скрипта заново, что позволяет модифицировать код без перезапуска всего сервера.

При разработке скрипта пользователю доступен API, позволяющий получать доступ к устройствам и хранилищам данных. В частности, скрипт может запрашивать данные из базы, получать уведомления о поступлении новых данных, посылать команды устройствам и т.д. Более подробно API описан в протоколе взаимодействия с локальным сервером.

Обработка сигналов

В описании API для некоторых объектов указан список сигналов. Это идеология, которая пришла из фреймворка Qt, который используется для разработки самого сервера. В целом механизм аналогичен callback-функциям. Для примера можно рассмотреть обработку сигнала newValueWritten от хранилища, который генерируется при появлении новых данных:

inStorage=SensorsDatabase.existingStorage({deviceId:deviceId,sensorName:sensorName});
inStorage.newValueWritten.connect(function(value)
{
    try
    {
        print("NEW VALUE: ");
        print("1");
        print(value.data);
        print("2");
        device.writeMeasurement("result",[value.data[0]+Math.random()]);
        print("3");
    }
    catch(e)
    {
        print("4");
        print(e.message);
    }
    finally
    {
        print("DONE");
    }
});

В данном примере запрашивается хранилище inStorage из базы хранилищ, и с помощью вызова inStorage.newValueWritten.connect добавляется обработчик. Каждый раз, когда в хранилище поступает новое значение, будет вызвана функция-обработчик, переданная в вызов connect. Если передать не анонимную функцию, а именованную, то в дальнейшем можно вызвать аналогичный метод disconnect, чтобы убрать обработчик.

Виртуальные устройства и сохранение результатов обработки

Разработчик скрипта может зарегистрировать на сервере вирутальное устройство, указав для него список датчиков и (опционально) описание интерфейса управления в xml формате. Это устройство может применяться в двух случаях:

  1. Для сохранения результатов обработки данных из скрипта. Для этого скрипт должен создать хранилище под датчик с этого устройства и генерировать "измерения" с виртуального устройства, передавая туда результаты обработки. Созданное хранилище может быть наравне с прочими использовано для обработки данных в самом сервере (получается цепочная обработка данных), а также привязано к какому-нибудь внешнему сервису для экспорта данных.
  2. Для обработки команд от пользователя. Виртуальное устройство наравне с остальными может получать команды от пользователя удаленно, либо через интерфейс консольного клиента или графического приложения. Эти команды могут использоваться для управления процессом обработки данных, или для генерации других команд реальным устройствам. Таким образом можно, например, задавать режимы работы каких-нибудь сложных систем.
Для обработки команд нужно установить обработчик с помощью метода setCommandCallback объекта виртуального устройства. Функция-обработчик принимает два параметра - команду и список аргументов, и возвращает либо значение типа bool (true/false), либо объект с ключами done типа bool и result. В result должен быть javscript-массив с результатами выполнения команды, если она предполагает возврат какого-нибудь результата.

Пример скрипта для обработки данных

//параметры датчика с реального устройства, значения которого мы будем обрабатывать
var deviceId="{f84526c1-5e88-4315-81f8-f7da45daa09d}";
var sensorName="blinks_count";

//параметры виртуального устройства, которое будет использоваться для сохранения результатов обработки и обработки команд от пользователя
var outDeviceId="{129ed0af-42ba-4f4e-a71c-1d0152abf930}";
var outSensorsXml="<sensors><sensor type=\"sv_f32\" name=\"result\"/></sensors>";//один датчик - result
var outControlsXml="<controls><group title=\"Device controls\"><control command=\"blink\" title=\"Blink\" button_text=\"blink\"/></group></controls>";//одна команда - blink

function setup()
{
    //отладка - выводим список всех имеющихся хранилищ
    var dbList=SensorsDatabase.listSensors();
    for(var i=0;i<dbList.length;++i)
    {
        print("device: "+dbList[i].deviceId);
        print("sensor: "+dbList[i].sensorName);
    }

    //регистрируем виртуальное устройство
    device=Devices.registerVirtualDevice(outDeviceId,"test.js-out",outSensorsXml,outControlsXml);
    if(!device)
    {
        print("ERROR: output device not registered!!!!");
        return;
    }

    //ищем хранилище для датчика с реального устройства
    inStorage=SensorsDatabase.existingStorage({deviceId:deviceId,sensorName:sensorName});
    if(inStorage==null)
    {
        print("No input storage found");
    }
    else
    {
        //выводим параметры хранилища
        print("db_values_type: "+inStorage.valuesType());
        print("db_device_name: "+inStorage.deviceName());
        print("db_values_count: "+inStorage.valuesCount());
        print("db_store_mode: "+inStorage.getStoreMode());

        //устанавливаем обработчик, которая будет вызываться сервером при появлении нового значения
        inStorage.newValueWritten.connect(function(value)
        {
            try
            {
                //выводим значение
                print("NEW VALUE: ");
                print(value.data);
                //производим "очень сложную" обработку и выдаем результат как "измерение" с датчика виртуального устройства
                device.writeMeasurement("result",{data:[value.data[0]+Math.random()]});
            }
            catch(e)
            {
                print(e.message);
            }
            finally
            {
                print("DONE");
            }
        });

        //ищем хранилище для результатов обработки
        outStorage=SensorsDatabase.existingStorage({deviceId:outDeviceId,sensorName:"result"});
        if(!outStorage)
        {
            print("No output storage found");
            //если не нашли - создаем
            outStorage=SensorsDatabase.createStorage({deviceId:outDeviceId,deviceName:"test.js-out",sensorName:"result",storeMode:"last_n_values",N:100});
            if(!outStorage)
            {
                print("ERROR: can't create storage for output values!!!!");
                return;
            }
        }
    }

    //устанавливаем функцию-обработчик команд
    device.setCommandCallback(function(cmd,args){
        if(cmd=="blink")//команда blink
        {
            //можно было бы мигнуть светодиодом, но у виртуального устройства его нет
            print("Wow, we have no led in a script, but we will try");
            print("Blink, BLINK!! Just do this!!!!");
            //команда "выполнена" успешно, возвращаем true
            return {done:true,result:["we did it, i think","however, we tried"]};
        }
        else return {done:false,result:["unknown command"]};//неизвестная команда
    });
};

setup();