Elastic:运用 Canvas 实时监控物联网设备状态并控制设备

在我还在 Ubuntu 公司工作的时候,我曾经做过 ubuntu core 物联网操作系统。在未来,物联网是一张比互联网更大的网。在物联网世界中,经常会收集大量的数据,比如温度,湿度,天气等传感器的数据,汽车及电梯的运行状况等等。当大量的数据被收集,我们可以通过数据平台对数据进行分析,并进行实时控制。在今天的文章中,我将分享一个简单的案例来展示如何使用 Canvas 对物联网设备进行监控。

在我们假想的案例中:我们家里有一些可控的灯,通过 Kibana 中的 Canvas 我们来监控等的状态,并通过 Canvas 的界面来控制灯的开关。

上面是一个简单的物联网示意图。这里的物联网设备可以是树莓派这样的设备。物联网设备通过 HTTP 和 Elastic Stack 进行连接,而物联网设备和灯之间的连接是靠 Zigbee 这样的无线连接来完成的。在今天的文章中,我们来介绍两个方面的内容:

  1. 从物联网向 Elastic Stack 发送数据,并由 Kibana 进行状态显示
  2. 从 Kibana 发送控制信号来控制灯的状态

 

从物联网设备发送状态给 Elastic Stack

在今天的实验中,我们使用一个简单的 nodejs 应用来发送等的状态。你可以在如下的地址下载我的模拟应用:

git clone https://github.com/liu-xiao-guo/elastic_iot

等我们下载完应用,我在 terminal 中进入该项目的根目录,然打入如下的命令:

npm install
npm start

这样我们的 nodejs 服务器就运行于 http://localhost:3000 这个地址上。这个应用就会不断地向 Elasticsearch 发送数据。我可以看一下其中的一个重要的文件:

routes/index.js

var express = require('express');
const elasticsearch = require('elasticsearch');
var router = express.Router();

var light_status = 1;

const client = new elasticsearch.Client({
  host: '127.0.0.1:9200',
  log: 'error'
});

function sendLightStatus() {
  console.log("light_status: " + light_status);

  client.index({  
    index: 'lights',
    body: {
      "id": 1,
      "status": light_status,
      "@timestamp": new Date(new Date().toUTCString())
    }
  },function( err,resp, status) {
      console.log(resp);
  });  
}

var mytimer = setInterval( function () {
  console.log("ticks for every 5 seconds");
  sendLightStatus();
}, 5000);


router.get('/toggle', function(req, res, next) {
  if(light_status == 1) {
    light_status = 0
  } else {
    light_status = 1
  }
  console.log("toggled: " + light_status);
  sendLightStatus();

  var data = {
    message: 'OK'
  };

  // res.status(200).send(data); 
  // res.sendStatus(200);
  // res.end();
});

/* GET home page. */
router.get('/', function(req, res, next) {
  console.log("/ Home page")
  res.render('index', { title: 'Express' });
});

router.get('/hello', function(req, res, next) {
  console.log("/hello")
  res.render('index', { title: 'Hello' });
});

module.exports = router;

在上面的代码中,我们使用了 light_status 来保存灯的状态。当 /toggle 接口被调用的时候,它会把 light_status 的值取反:开灯的状态变为关灯,关灯状态变为开灯。当然在实际的使用中,我们可以调用 Zigbee 这样的无线软件栈来控制我们的灯。我们使用如下的代码:

var mytimer = setInterval( function () {
  console.log("ticks for every 5 seconds");
  sendLightStatus();
}, 5000);

定时每隔5秒中的时间发送 light_status 的状态给 Elasticsearch,这样的数据在 Elasticsearch 中就是一个时序的数据。数据保存在一个叫做 lights 的索引之中:

发送的数据具有如下的格式:

          "id" : 1,
          "status" : 0,
          "@timestamp" : "2020-11-16T11:27:04.000Z"

这里的 id 表示灯的 id,status 则表示状态:1 为开,0 为关。 我们使用 @timestamp 表示发送的时间戳。

我们可以通过对 lights 的过去5秒中的数据进行 SUM,也就是求和。如果求和的值大于0,那么就表示灯是亮着的,如果是0,则表示灯是灭的。

 

通过 Canvas 实时展示灯的状态

我们接下来运用 Canvas 来展示灯的状态:

为了能够使用我们自己定义的图片,我们可以通过 manage assets 来上传我们的图片:

我们记下这两个图片的 ID。这上面的两个图片可以在我之前的 elastic_iot 项目中的 images 目录中找到。点击 Close 按钮:

标题

我们重新回到之前的那个插入 image 的那个画面,点击 Asset,并选择 light_off  的图像:

我们接着调整图像的大小,并点击右下角的 Expression editor:

我们把如下的一些代码拷贝进入编辑中:

filters
| timefilter from="now-10s" to="now"
| essql query="SELECT SUM(status) FROM \"lights\" WHERE id = 1"
| getCell "SUM_status_"
| if {gt 0} 
  then={image dataurl={asset "asset-5df3b112-784b-4a59-a004-1bfc03b537de"} mode="contain"} 
  else={image dataurl={asset "asset-674cd68c-66af-4acd-ad78-47ce1ae3add9"} mode="contain"}
| render 

在上面我们要注意换掉上面的 asset ID 为自己 Kibana 中的所生成的 asset ID。位于上面的第一 ID 是 light_on 的 ID,而下面的这个 ID 是 light_off 的 ID。上面的代码其实很简单,就是从 lights 索引中获得从现在到过去10s的数据,然后相加。如果加的结果是大于0的,那么就灯亮了。反之,就是灯灭了。我们也可以从如下的 data 中看到 filter 是如何写的:

点击 Run 按钮:

我们接着点击 Auto refresh settings:

我们设置两秒更新一次。这样我们就可以看到灯是亮着的。这是因为我们的 elastic_iot 里的默认值就是1的缘故。

 

调用 toggle 接口来改变等的状态

我们接下来想通过调用 /toggle 接口来开关我们的灯。这个实现虽然有些 tricky,但是我们可以通过 Markdown 的链接来实现,虽然不是很完美:

点击 Expression editor,并粘贴如下的代码:

filters
| demodata
| markdown "[link](http://localhost:3000/toggle)"
| render 
  css=".canvasRenderEl a {
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  border-radius: 90px;
  text-indent: -10000px;
}
.canvasRenderEl a:hover {
  background: rgba(0,0,0,.2);
}"

点击 Run 按钮:

我们需要调整这个 markdown 的大小和灯的一样即可。

这样当我们点击这个等的图像时,它会调用 http://localhost:3000/toggle 接口改变等的状态。我们接着会看到灯的状态发送改变。

目前的这个设计有一个不好的地方是我们可以看到不断旋转的图标在网页的标题处出现,表明该网页一直在等响应。这是由于我在之前的 nodejs 代码中:

router.get('/toggle', function(req, res, next) {
  if(light_status == 1) {
    light_status = 0
  } else {
    light_status = 1
  }
  console.log("toggled: " + light_status);
  sendLightStatus();

  var data = {
    message: 'OK'
  };

  // res.status(200).send(data); 
  // res.sendStatus(200);
  // res.end();
});

没有返回任何的值。这显然不是一个很好的设计。但是如果我添加一个响应值,那么网页会自动跳转到另外一个 tab 去显示当前的返回值。用户体验更差一些。目前我还没有好的办法来解决这个问题。