Работа со встроенными модулями

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

Встроенные библиотеки необходимо специально запрашивать. Функция импортирования встроенных модулей создана для того, чтобы в процессе запуска и выполнения алгоритма, заданного в обработчике, не приходилось обращаться к избыточным ресурсам. Те ресурсы, которые были вызваны, но которые не используются, могут замедлять процесс обработки, чего лучше стараться избегать.

Вызов любого из встроенных модулей осуществляется следующим образом. В начале необходимо создать новую переменную типа данных const, затем ей необходимо присвоить функцию на запрос, в которой указывается наименование необходимого модуля или указатель на него:

const m = require('name_of_module');

В процессе разработки исходного кода для обработчика могут быть добавлены следующие модули:

Примеры загрузки и использования встроенных модулей приведены в репозитории на GitHub.

Math.js

Math.js - это расширенная библиотека, находящаяся в открытом доступе и используемая для JavaScript и Node.js. Она обладает широким набором встроенных функций и математических констант, позволяя производить огромный комплекс различных математических операций над большими числовыми данными, комплексными числами, дробями, матрицами и т. д. Возможности библиотеки являются достаточно обширными, что позволяет упрощать математические вычисления в ходе выполнения алгоритма.

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

Для того чтобы загрузить данный встроенный модуль, необходимо локально в функции или глобально вызвать его:

const math = require('mathjs');

Далее обращение к библиотеке осуществляется через переменную math.

После вызова модуля можно использовать все функции, встроенные в данную библиотеку.

Например, при обработке данных необходимо вычислить производную какой-либо величины. Тогда нужно создать новый обработчик. В нем нужно:

// calculates a derivative

const math = require('mathjs');

function process(a) {
  let derivative = math.derivative('a^3 + 4*a^2 + 7', 'a');     //3*a^2 + 8*a
  derivative = derivative.eval({a});
  const da = derivative.toString();
  return { da };
}

Buffer

Этот модуль представляет собой класс Buffer, представленный как часть API Node.js, которая позволяет работать и взаимодействовать с потоками различных двоичных данных. Данный класс работает с объектами типа TypedArray, которые визуально похожи на массивы двоичных данных, но применяются именно при работе с буферами. Модуль Buffer может оказаться полезным при обработке данных именно по причине работы с потоками. Ведь, когда речь идет о потоках, подразумевается, что происходит отправка и перемещение данных. Например, зачастую необходимо передать данные на обработку, сделать их доступными для считывания. В таких случаях, как правило, возникают очереди пакетов данных, запрошенных на обработку. Эта очередь выстраивается в буфере.

Буфер представляет собой некую часть оперативной памяти, в которой накапливаются данные, выстраиваются в очередь в соответствии со временем отправки запроса и на выходе отправляются на обработку друг за другом. Технологии Node.js предусмотрены так, что они позволяют создать и отправить запрос, однако, не могут контролировать потоки данных. С этой целью уже применяются буферы.

При работе с технологиями Интернета вещей может возникнуть необходимость в обработке данных по определенному протоколу. Например, от устройства по протоколу могут быть отправлены данные в виде последовательности шестнадцатеричных чисел. Они требуют дальнейшей обработки, в частности, парсинга. Данные могут накапливаться и ожидать своей очереди на обработку. Для того чтобы эту очередь выстроить и управлять ею, можно воспользоваться обработчиком, внутри которого реализована работа с буфером.

Она начинается с вызова встроенного модуля Buffer:

const { Buffer } = require('buffer');

Внутри функции создается буфер. При его создании могут быть указаны входные данные в качестве содержимого буфера, кодировка данных, размерность буфера (количество байт). В рассматриваемом примере при создании двух буферов в них изначально записываются входные данные, т. е. значения определенных параметров модели. Далее суммируются их размерности. После чего буферы объединяются в один. Для корректного вывода содержимого буфера его необходимо декодировать.

// creates two buffers and combines them

const { Buffer }  = require('buffer');

function process(a, b) {
  const buf_a = Buffer.from(a);
  const buf_b = Buffer.from(b);
  const totalLength = buf_a.length + buf_b.length;
  const buf_sum = Buffer.concat([buf_a, buf_b], totalLength).toString();
  return { buf_sum };
}

History

Встроенный модуль history позволяет управлять историей и забирать последние пакеты данных. Он возвращает массив объектов, в котором содержатся только данные, отправленные ранее, не включая текущий пакет. Количество пакетов, которые могут быть извлечены из истории, по умолчанию равно 50. Однако при необходимости пользователь может сам задать нужное ему число пакетов. Модуль history может быть полезен при анализе данных, при сравнении текущих данных с предыдущими, для некоторых расчетов на основе предыдущих значений параметров и т. п.

При работе с данным модулем изначально необходимо его вызвать. Стоит учитывать, что вызов встроенных модулей reject, history и split осуществляется через указатель @ric/handler/. Например,

const history = require('@ric/handler/history');

Далее загружаются пакеты данных из истории. В методе history.get() для выгрузки нескольких пакетов данных указываются одна или несколько переменных, соответствующие определенным входным или выходным параметрам функции, и число выгружаемых пакетов, одинаковое для всех указанных параметров. Метод history.last() позволяет выгрузить только последний пакет данных, хранящийся в истории. При этом любая из двух указанных функций может вернуть пустой массив объектов, если история для указанного параметра на текущий момент еще не была записана.

Входными данными для функции, приведенной в примере использования метода history.get(), является переменная a. Ей соответствует определенный параметр модели объекта. В указанном примере рассчитывается среднее арифметическое десяти значений переменной a, поэтому можно считать, что ей присваивается значение числового аргумента. Соответственно, для данного параметра выгружается девять последних пакетов из истории. Далее рассчитывается среднее арифметическое значений числового аргумента на основе одного текущего и девяти предыдущих значений.

// calculates the average value

const history = require('@ric/handler/history');

function process(a) {
  const packets = history.get(['a'], 10);
  let avg = a;
  for (let i = 0; i < packets.length; i++) {
      avg += packets[i];
  }
  avg /= (packets.length + 1)
  return { avg }
}

В примере использования метода history.last() рассчитывается значение скорости, скалярное значение которого вычисляется как разность перемещения, деленная на время. В данном случае переменная времени является параметром по умолчанию, ее значение фиксировано. Разность перемещения рассчитывается как разность текущего и предыдущего значения. При этом осуществляется проверка на наличие поля в объекте. Если оно есть и объект не пустой, то тогда переменной prevS присваивается значение, хранящееся в последнем пакете данного параметра в истории. Если же объект пустой и история еще не была сохранена, то тогда значение этой переменной останется равным нулю.

// calculates a speed

const history = require('@ric/handler/history');

function process(t = 5, s) {
  let prevS = 0;  
  const last = history.last(['s']);
  if ('s' in last) prevS = +last.s;
  const v = (s - prevS) / t;
  return { v };
}

Модуль history позволяет выгружать массив объектов. Соответственно, нужно помнить, что к каждому загруженному значению обращение осуществляется как к элементу массива. При этом необходимо учитывать особенности работы с объектами.

Reject

Модуль reject встраивается в обработчик в том случае, если в очереди обрабатываемых пакетов данных встречается такой пакет, обработка которого не может быть осуществлена. В этом случае пакет отбрасывается из очереди, его обработка производиться не будет. Алгоритм обработки будет применена к последующему пакету, как только он будет получен. Отмена операции обработки выполняется на основе указанного в исходном коде условия. Данный модуль находит достаточно широкое применение. Он может быть использован для реализации фильтрации, для получения актуальных данных и отбрасывания устаревших и т. п.

К примеру, фильтрация реализуется следующим образом. Иногда от датчиков могут поступать некорректные данные, например, датчик расстояния может присылать отрицательное значение измеренного расстояния. При этом пройденное объектом расстояние должно быть использовано для дальнейшей обработки. В таком случае отрицательные значения будут искажать расчеты, и обработка данных будет совершаться некорректно. Соответственно, отрицательные значения пройденного расстояния нужно отбрасывать. Для этого применяется функция reject(). При этом неотрицательные текущие значения записываются в массив, который может быть передан на дальнейшую обработку.

// makes validation of data and reject if they does not meet the specified condition

const reject = require('@ric/handler/reject');

function process(d) {
  const distance = [];
  if (d < 0) return reject();
  distance.push(d);
  return { distance };
}

Split

В некоторых случаях при обработке данных может потребоваться передать полученные данные в виде нескольких отдельных пакетов по различным каналам. С этой целью может быть использован модуль split. Он позволяет выходные данные разбивать на несколько пакетов. К примеру, в результате выполнения прописанного в обработчике алгоритма на выходе должен получиться массив данных. Если к этому массиву применить функцию split, то тогда будет сформировано несколько пакетов данных, количество которых будет равно длине полученного массива, при этом в каждом пакете будет храниться отдельный элемент массива.

Например, необходимо высчитать значение эффективной температуры для двадцати последних пакетов данных. Эффективная температура рассчитывается по формуле:

ЭТ = t - 0,4(t - 10)(1 - f/100),

где ЭТ - эффективная температура;   
t - температура воздуха;  
f - влажность воздуха.

Необходимо, чтобы значение эффективной температуры было рассчитано для двадцати последних пакетов. В итоге должен быть сформирован массив, который на выходе из функции нужно разбить на отдельные пакеты данных с целью использования каждого элемента массива по отдельности. В этом случае в функции прописывается алгоритм реализации и заполнения данного массива. Стоит учитывать, что данные, выгруженные из истории, могут быть различных типов данных. Поэтому для числовых расчетов необходимо в явном виде задавать тип данных для каждого элемента загруженных массивов. В итоге в конце функция возвращает split(), что означает передачу, в данном случае, двадцати пакетов на выходе.

// splits the data

const history = require('@ric/handler/history');
const split = require('@ric/handler/split');

function process(temp, humid) {
  const temp_last = history.get(['temp'], 20);
  const humid_last = history.get(['humid'], temp_last.length);
  const eff_temp = [];
  for (let i = 0; i < temp_last.length; i++)
    eff_temp.push(+temp_last[i] - 0.4*(+temp_last[i] - 10)*(1 - +humid_last[i]/100));
  return split(eff_temp);
}