Обработка данных мониторинга жилых помещений с помощью службы Time Series Database в Bluemix

Comments

С ростом популярности средств домашней автоматизации растет количество датчиков для записи статистических данных и объем информации, необходимой для передачи этих данных. С помощью службы Time Series Database в BlueMix можно упростить процессы регистрации данных, а также обращения к этим данным и составления отчетов. В этом руководстве мы рассмотрим, как создавать и хранить информацию и в конечном итоге получать нужные отчеты, используя службу Time Series Database. Мы также будем использовать эту службу для сопоставления данных, поступающих от нескольких датчиков, для отслеживания эффективности систем отопления в доме со многими помещениями.

Запустить приложениеПолучить код

Создание основного приложения

Ядром нашего приложения служит программа Node.js, поддерживающая связь с серверной частью, в качестве которой выступает служба Time Series Database.

Для начала создадим на панели управления BlueMix новое приложение Node.js:

  1. Войдите в Bluemix.
  2. Нажмите кнопку Create an APP.
  3. Нажмите на значок Node.js App Starter и настройте параметры приложения.

Создав приложение и присвоив ему имя, добавьте новую службу – Time Series Database.

Теперь нужно получить доступ к коду. Я предпочитаю загрузить пакет, а затем использовать для взаимодействия с процессом инструмент командной строки cf. Это позволяет быстро вносить изменения и с помощью команды cfpush обновлять версию своего экземпляра BlueMix.

Как бы вы не работали с кодом, ядром вашего приложения будет файл app.js, созданный в каталоге верхнего уровня пакета приложения.

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

/*jshint node:true*/ 
 
// app.js 
// Этот файл содержит серверный код приложения на языке JavaScript. 
// В этом примере в качестве платформы веб-приложения используется express (http://expressjs.com/), 
// а в качестве шаблонизатора – jade (http://jade-lang.com/). 
 
var express = require('express'); 
 
// настройка промежуточного ПО 
var app = express(); 
var fs = require('fs'); 
app.use(app.router); 
app.use(express.errorHandler()); 
app.use(express.static(__dirname + '/public')); //setup static public directory 
app.set('view engine', 'jade'); 
app.set('views', __dirname + '/views'); //optional because express defaults to CWD/views 
 
// отображение страницы index  
app.get('/', function(req, res){ 
	res.render('index'); 
}); 
 
// В файле process.env имеется много полезных переменных среды. 
// VCAP_APPLICATION содержит полезные сведения о развернутом приложении. 
var appInfo = JSON.parse(process.env.VCAP_APPLICATION || "{}"); 
// TODO: Получить сведения о приложении и использовать их. 
 
// Переменная VCAP_SERVICES содержит все учетные данные служб, 
// привязанных к этому приложению. Подробнее о ее содержании см. в 
// документации или в примере каждой службы. 
var services = JSON.parse(process.env.VCAP_SERVICES || "{}"); 
// TODO: Получить учетные данные службы и установить связь со службами bluemix. 
 
// IP-адрес Cloud Foundry DEA (Droplet Execution Agent), где размещается это приложение: 
var host = (process.env.VCAP_APP_HOST || 'localhost'); 
// Порт DEA для связи с приложением: 
var port = (process.env.VCAP_APP_PORT || 3000);

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

var services = JSON.parse(process.env.VCAP_SERVICES || "{}");

Информация, полученная этим процессом, включает в себя сведения для доступа ко всем службам, настроенным в приложении. В частности, в ней содержится URL-адрес и реквизиты доступа к вашему экземпляру Time Series Database.

Еще один важный фрагмент определяет, что происходит при обращении к определенному URL-адресу в вашем приложении:

// отображение страницы index  
app.get('/', function(req, res){ 
	res.render('index'); 
});

В данном случае всякий раз при обращении к myapp.bluemix.net/ (root или index) приложение Node.js может непосредственно ответить или вызвать другую функцию. Здесь мы вызываем другую функцию, используя встроенное определение функции JavaScript, вызывающее функцию render(). Это метод объекта res (response), который, по существу, считывает содержимое файла и возвращает его в документ index.

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

Независимо от ответа на каждый из этих элементов, важно то, что функция app.get() определяет маршрут, который ставит URL-адрес доступа к операции в соответствие функции или странице, которую нужно возвратить. Помните, что мы выполняем это приложение, по существу, внутри приложения JavaScript, поэтому нужен способ установления связи между целевым URL-адресом и ответом. Метод app.get() – это метод регистрации URL и того, что этот URL означает для приложения.

Последний раздел файла app.js содержит функцию, которая запускает приложение (и процесс ожидания входящих запросов), а также строку, которая регистрирует запуск приложения:

app.listen(port, host); 
console.log('App started on port ' + port);

Опять же, имейте в виду, что это JavaScript-приложение, поэтому никаких stdout или stderr нет. Вместо этого ошибки и информация регистрируются с помощью метода console.log(). В BlueMix-приложении эта информация доступна на панели управления и с помощью вызова 'cf log appname' из инструмента командной строки cf.

Прежде чем приступить к обработке данных, откроем соединение с нашей службой Time Series Database.

Установление связи с Time Series Database

К службе Time Series Database в BlueMix можно обращаться через различные интерфейсы в зависимости от среды приложения. Для приложения Node.js существуют два решения: REST API и API Mongo. Интерфейс REST полезен, когда нужен прямой интерфейс к данным, например, при извлечении графической и другой информации.

Мы используем Mongo API, так как это очень простой способ доступа к данным нашего приложения Node.js с использованием JSON-вызовов для описания, разграничения и определения записей и информации.

Чтобы создать интерфейс, нужны реквизиты доступа к базе данных (URL-адрес, включая имя пользователя и пароль). Эта информация содержится в переменной службы, которая была заполнена автоматически в скрипте Node.js. Чтобы работать с несколькими службами, необходимо подключиться к первому URL внутри объекта timeseriesdatabase главного объекта службы:

services.timeseriesdatabase[0].credentials.json_url

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

require('mongodb').connect(services.timeseriesdatabase[0].credentials.json_url, function(err, conn) { 
… 
}

Этот код открывает соединение через API MongoDB со службой Time Series Database с помощью извлеченных реквизитов доступа путем вызова функции, которая предоставляет объекты ошибок и соединения. Несмотря на кажущуюся расточительность, это наилучший метод регистрации любых проблем и ошибок и восстановления после них.

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

Хранение записей Time Series

Информация, хранящаяся в Time Series Database, предназначена для использования с помощью определенных средств и в определенной среде. Time Series Database можно рассматривать как записи в дневнике, где вы записываете, где вы находитесь, способ измерения и значение. Так как это база данных временных рядов, она позволяет наблюдать, как это общее значение изменяется со временем, определять, направлена ли тенденция измерений вверх или вниз, и сравнивать последние измерения с историческими значениями.

В силу принципа работы Time Series Database имеются некоторые ограничения и параметры, которые нужно вводить для каждого измерения:

  • должны быть указаны единицы измерения (measure_unit). В данном случае это градусы Цельсия, но могут быть и мегабайты, секунды или миллиметры;
  • должно быть указано местоположение или лицо (loc_esi_id). Это служит идентификатором данной записи;
  • должно быть указано направление (direction): служит ли элемент производителем (то есть источником значений) или потребителем. Это значение в один байт: P или C;
  • должно быть указано само значение (value). Нельзя записать значение 'empty' или NULL. Значение записывается как десятичный тип с точностью до 3 значащих цифр после десятичной точки;
  • должна быть отметка времени считывания (tstamp). Она имеет формат 2014-01-01 00:00:00.00000.

В отличие от традиционной базы данных, отметка времени указывается в единицах, кратных 15. Таким образом, можно записать значение 00, 15, 30 или 45 минут после каждого часа. К тому же можно записать только одно значение каждой отметки времени, направления, единиц измерения и местоположения. Например, при записи температуры в гостиной от датчика температуры в 16:11:00 в определенный день это будет единственной такой точкой данных. Однако можно записать температуру в гараже на тот же момент времени, потому что местоположение будет отличаться.

Все данные записываются в коллекцию ts_data_v. Так как в эту таблицу можно записывать данные из разных мест, нам вряд ли понадобится больше одной таблицы, так как можно просто использовать другое местоположение или другой тип измерения. Однако имейте в виду, что нельзя записать две температуры (тип измерения C) в одном и том же месте (в гостиной), но ничто не мешает использовать местоположения 'lounge_front' (передняя часть гостиной) и lounge_front' (задняя часть гостиной).

Таким образом, можно создать следующую запись:

{ 
   "direction" : "P", 
   "tstamp" : "2014-09-16 11:17:15.00000", 
   "measure_unit" : "C", 
   "value" : 21, 
   "loc_esi_id" : "lounge" 
}

Чтобы записать значение, необходимо выполнить разбор URL в приложении, выделив местоположение и значение. Это можно сделать путем обработки объекта запроса из вызова (из app.get()):

var parsedUrl = require('url').parse(req.url, true); 
var queryObject = parsedUrl.query; 
var name = (queryObject["name"] || 'lounge'); 
var temppoint = parseFloat((queryObject["temp"] || 0));

Сначала разберем URL из объекта запроса. Это приведет к созданию объекта queryObject, из которого можно извлечь значения URL, в данном случае, местоположение и температуру. Таким образом, следующая строка:

http://mcslptds.mybluemix.net/reading?name=kitchen&temp=23

дает нам местоположение 'kitchen' и значение kitchen'.

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

Вместе с извлечением значений из URL-адреса получим:

var create_datapoint = function(req, res) { 
  console.log("Recording a datapoint"); 
  require('mongodb').connect(services.timeseriesdatabase[0].credentials.json_url, function(err, conn) { 
 
  if (err) { res.write(err.stack); } 
  console.log("Extracting values"); 
 
  var collection = conn.collection('ts_data_v'); 
  var parsedUrl = require('url').parse(req.url, true); 
  var queryObject = parsedUrl.query; 
  var name = (queryObject["name"] || 'lounge'); 
  var temppoint = parseFloat((queryObject["temp"] || 0)); 
 
  var tsdate = new Date(); 
 
  var datestring = tsdate.getUTCFullYear() + '-'; 
 
  if (parseInt(tsdate.getUTCMonth() + 1) < 10) { 
  datestring = datestring + '0' + (tsdate.getUTCMonth()+1) + '-'; 
  } 
  else { 
  datestring = datestring + (tsdate.getUTCMonth()+1) + '-'; 
  } 
 
  if (parseInt(tsdate.getUTCDate()) < 10) { 
  datestring = datestring + '0' + tsdate.getUTCDate() + ' '; 
  } 
  else { 
  datestring = datestring + tsdate.getUTCDate() + ' '; 
  } 
 
  if (parseInt(tsdate.getUTCHours()) < 10) { 
  datestring = datestring + '0' + tsdate.getUTCHours() + ':'; 
  } 
  else { 
  datestring = datestring + tsdate.getUTCHours() + ':'; 
  } 
 
  // Minutes should only be logged if they are are a multiple of 15 
 
  var realminutes = (parseInt(tsdate.getUTCMinutes()/15)*15); 

  if (realminutes < 10) { 
  datestring = datestring + '00:'; 
  } 
  else { 
  datestring = datestring + realminutes + ':'; 
  } 
 
  datestring = datestring + '00:00000'; 

  console.log("Date: " + datestring); 
 
  var message = { 'loc_esi_id': name, 'measure_unit' : 'C', 'direction' : 'P', 'value': temppoint, 'tstamp': datestring}; 
  console.log("Constructed record"); 
  console.log(JSON.stringify(message)); 
 
  collection.insert(message, {safe:true}, function(err){ 
  if (err) { console.log(err.stack); } 
  res.write(JSON.stringify(message)); 
  }); 
  }); 
  res.end(); 
};

Это даст нам функцию create_datapoint(), которая нужна для добавления маршрута к данному приложению, так что нужно добавить подходящую запись по другим маршрутам:

app.get("/reading", function (req, res) { 
  create_datapoint(req,res); 
  });

Теперь у нас есть способ получения данных, и мы можем протестировать его, снова развернув приложение в BlueMix и воспользовавшись curl для записи одной точки:

$ curl 'http://mcslptds.mybluemix.net/reading?name=kitchen&temp=23'

Теперь, когда у нас есть способ ввода данных, посмотрим, как их выводить.

Получение записей

Чтобы получить листинг записей, нужно подключиться к базе данных с помощью интерфейса MongoDB, а затем использовать функцию collection.find() для извлечения значений, хранящихся в базе данных:

var list_datapoint = function(req, res) { 
  var parsedUrl = require('url').parse(req.url, true); 
  var queryObject = parsedUrl.query; 
  var name = (queryObject["name"] || 'lounge'); 
 
  res.writeHead(200, {'Content-Type': 'text/plain'}); 
 
  require('mongodb').connect(services.timeseriesdatabase[0].credentials.json_url, function(err, conn) { 
  var collection = conn.collection('ts_data_v'); 
  res.write("Reading from collection ts_data_v"); 
  collection.find({"loc_esi_id":name}, {limit:1000, sort:[['loc_esi_id','ascending'],['tstamp','ascending']]}, function(err, cursor) { 
  cursor.toArray(function(err, items) { 
  if (err) { res.write(err.stack); } 
  for (i=0; i < items.length; i++) { 
  res.write(JSON.stringify(items[i]) + "\n"); 
  } 
  res.end(); 
  }); 
  }); 
  }); 
};

Сначала получим действительный заголовок (HTTP 200). Затем подключимся к базе данных и выполним запрос, используя полученное имя или имя по умолчанию 'lounge'.

Для удобства давайте зарегистрируем эту функцию в приложении с помощью URL /dumplist:

app.get("/dumplist", function (req, res) { 
  list_datapoint(req,res); 
  });

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

Построение графика

Построение графика наших данных температуры фактически состоит из двух шагов:

  • создание метода построения списка значений, подходящего для графика;
  • создание HTML-страницы, содержащей программное обеспечение для построения графика и вызова URL-адреса для получения данных.

Сначала решим вторую задачу – создадим статическую HTML-страницу, которая будет анализироваться и отображаться пользователю при обращении к определенному URL-адресу:

var graph_view = function(req, res) { 
  fs.readFile('./public/graph.html', function(err, data) { 
  res.end(data); 
  }); 
}

Она возвращает содержимое файла /public/graph.html из каталога приложения. Чтобы получить изображение, добавим к этой функции маршрут /graph:

app.get("/graph", function (req, res) { 
  graph_view(req,res); 
  });

Сам код HTML довольно прост. Загрузите JQuery, а затем библиотеку JQuery FLOT для вывода графической информации:

&lt;html&gt; 
&lt;head&gt; 
&lt;title&gt;Graph&lt;/title&gt; 
&lt;link rel="stylesheet" type="text/css" href="stylesheets/style.css" /&gt; 
&lt;/head&gt; 
&lt;body&gt; 
&lt;script src="jquery.js"&gt;&lt;/script&gt; 
&lt;script src="jquery.flot.js"&gt;&lt;/script&gt; 
&lt;script src="jquery.flot.time.js"&gt;&lt;/script&gt; 
 
&lt;script&gt; 
  $(document).ready(function() { 
  var dataseries = []; 
 
  $.getJSON("/graphpoints", function(json) { 
  $.plot($("#plot") , [json],{ xaxis: { mode: "time"}}); 
  }); 
 
}); 
&lt;/script&gt; 
 
&lt;div id="plot"&gt;&lt;/div&gt; 
&lt;div id="plotdata"&gt;&lt;/div&gt; 
 
&lt;/body&gt; 
&lt;/html&gt;

Добавьте этот код в нужное место относительно ранее вызванной функции parse().

Важная часть кода – функция inline, которая определяет данные. В нашем случае мы загружаем их посредством Ajax с помощью вызова своего собственного приложения через URL /graphpoints:

   $.getJSON("/graphpoints", function(json) { 
  $.plot($("#plot") , [json],{ xaxis: { mode: "time"}}); 
  });

Информация должна быть в определенном формате, содержащем массив массивов, в котором каждый вложенный массив представляет собой пару значений X и Y. Например:

[[1,21],[2,23],…]

Этот первый элемент представляет собой порядковый номер или строку дата/время, которую можно разложить на данные с помощью библиотеки Flot.

Для их вывода нужно создать модифицированную версию нашей функции /dumplist, которая создает информацию в нужном формате.

Вся функция будет выглядеть так:

var graph_datapoints = function(req, res) { 
  var parsedUrl = require('url').parse(req.url, true); 
  var queryObject = parsedUrl.query; 
  var name = (queryObject["name"] || 'lounge'); 
 
  res.writeHead(200, {'Content-Type': 'application/json'}); 
 
  require('mongodb').connect(services.timeseriesdatabase[0].credentials.json_url, function(err, conn) { 
  var collection = conn.collection('ts_data_v'); 
  var dataseries = new Array(); 

  collection.find({"loc_esi_id": name}, {limit:200, sort:[['tstamp','descending']]}, function(err, cursor) { 
    cursor.toArray(function(err, items) { 
    if (err) { res.write(err.stack); } 
    for (i=0; i < items.length; i++) { 
    timeint = (new Date(items[i].tstamp).getTime())/1000; 
    dataseries.push([timeint,items[i].value]); 
    console.log(JSON.stringify(dataseries)); 
    } 
    console.log("Final: " + JSON.stringify(dataseries)); 
    res.write(JSON.stringify(dataseries)); 
    res.end(); 
    }); 
  }); 
  }); 
}

В основе функции лежит обращение к API Mongo для доступа к коллекции. Коллекция автоматически сортируются по полю отметки времени в порядке убывания. Затем для каждой возвращенной записи создается массив, содержащий строку даты/времени, полученную из отметки времени:

    timeint = (new Date(items[i].tstamp).getTime())/1000; 
    dataseries.push([timeint,items[i].value]);

При обращении к странице графика возвращается HTML-страница, и код Ajax загружает URL /graphpoints, который возвращает данные из Time Series Database, выстраивая значения и отображая график.

Заключение

Создать новое приложение в BlueMix с помощью Node.js довольно легко. Используя готовые шаблоны содержания и приложений, можно с легкостью составлять простые приложения. Ключевыми элементами приложений Node.js служат REST-подобные API, которые можно создавать для подачи и приема информации, а также способ анализа информации для отображения традиционных HTML-страниц. Комбинируя эти элементы с серверной базой данных, такой как Time Series Database, очень легко хранить и извлекать эту информацию. Time Series Database устраняет трудности, связанные с правильным построением и упорядочением данных.


Ресурсы для скачивания


Похожие темы

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Большие данные и аналитика, Облачные вычисления
ArticleID=1020127
ArticleTitle=Обработка данных мониторинга жилых помещений с помощью службы Time Series Database в Bluemix
publish-date=11062015