首页 > 其他分享 >使用MASA Stack+.Net 从零开始搭建IoT平台 第四章 4.4 查询历史数据

使用MASA Stack+.Net 从零开始搭建IoT平台 第四章 4.4 查询历史数据

时间:2023-07-19 17:55:43浏览次数:48  
标签:4.4 MASA DeviceName IoT private 查询 new

@

目录


前言

IoT平台需要监控设备的运行状态,统计和分析设备传感器数据,使用图表展示是比较常见的场景。使用图表和表格数据组合的Dashboard也可以放在首页作为大屏展示。

分析

因为我们设备上报的数据都是存储到时序库influxdb中的,所以我们按照时间统计数据是很方便的,但是设备上报的数据频率和我们需要的统计周期可能并不一致,例如设备5s上报一次传感器数据,但是我们希望2小时统计一次这两小时内的最高值、最低值,或者平均值。查看文档发现aggregateWindow函数可以满足我们的需求,也就是所有的计算都可以在influxdb中完成。结合MASA Blazor现成的ECharts组件可以轻松完成图表制作。

方案

我们可以现在influxdb的UI管理界面点击DataExplorer中调试我们的查询脚本,我提前准备了一些数据

from(bucket: "IoTDemos")
|> range(start: 2023-07-17T16:00:00Z,stop:2023-07-18T16:00:00Z)                                                                                          
|> filter(fn: (r) => r._measurement == "AirPurifierDataPoint" 
              and r.ProductId == "c85ef7e5-2e43-4bd2-a939-07fe5ea3f459" 
              and r.DeviceName == "284202304230001")
|> aggregateWindow(every: 2h, fn: mean)
|> fill(value: 0.0)

我们这里只对接下来用到的一个查询语句做简单介绍,其他语法与函数请参考influxdb官方文档

https://docs.influxdata.com/influxdb/v2.7/query-data/

  1. from(bucket: "IoTDemos")表示我们要查询的库。
  2. |> range(start: 2023-07-17T16:00:00Z,stop:2023-07-18T16:00:00Z) 代表我们查询的时间范围,这里时间范围还有很多写法,例如range(start: -10h)代表查询最近十小时之内的数据,这里有个需要注意的地方,range(start: -1d),可以这样写来查一天之内的数据,但是这一天是按照UTC时间来统计的。
  3. filter(fn: (r) => r._measurement == "AirPurifierDataPoint"
    and r.ProductId == "c85ef7e5-2e43-4bd2-a939-07fe5ea3f459"
    and r.DeviceName == "284202304230001")

    filter过滤函数,可以限定我们的查询条件,这里的条件为从AirPurifierDataPoint表中,并限定了设备名称和产品ID,这里如果只查一个字段可以添加查询条件,例如添加 and r._field == "PM_25",将只返回PM_25的值
  4. |> aggregateWindow(every: 2h, fn: mean) 由于设备上报数据比较频繁(我这里模拟的数据是5s上报一次),但是当我展示给用户图表的时候,我不希望上面有那么密集的点,这时候就可以使用aggregateWindow函数,每2小时统计一次平均值,mean代表算数平均值。这样我查询24小时数据最多可以得到12个数据点,每个点为这两小时数据的平均值。
  5. |> fill(value: 0.0) 如果某个时间范围没有数据,那么这个时间范围统计出来的平均值会是"null",而不是0,我们可以使用这个函数将null替换为0.

我们执行一下,可以看到得到了36条数据(共3个字段,每个字段一天12条汇总数据)

编写代码

定义数据类

我们先定义一个ECharts需要的数据类

    public class EChartsData
    {
        /// <summary>
        /// 设备名称
        /// </summary>
        public string DeviceName { get; set; }

        public List<FieldData> FieldDataList { get; set; }
    }

    public class FieldData
    {
        /// <summary>
        /// 字段名称
        /// </summary>
        public string FieldName { get; set; }

        /// <summary>
        /// 时间点列表(X轴)
        /// </summary>
        public List<DateTime> DateTimes { get; set; }

        /// <summary>
        /// 数据点列表(Y轴)
        /// </summary>
        public List<double> Values { get; set; }
    }

编写查询方法

然后我们在MASA.IoT.Core项目的TimeSeriesDbClient类中添加一个查询的方法

  public async Task<EChartsData> GetDeviceDataPointListAsync(GetDeviceDataPointListOption option)
        {
            var query =
                $@"from(bucket: ""{_bucket}"")
                    |> range(start: {option.UTCStartDateTimeStr},stop:{option.UTCStopDateTimeStr})                                                                                          
                    |> filter(fn: (r) => r._measurement == ""AirPurifierDataPoint"" 
                    and r.ProductId == ""{option.ProductId}"" 
                    and r.DeviceName == ""{option.DeviceName}"")
                    |> aggregateWindow(every: 2h, fn: mean)
                    |> fill(value: 0.0)";
            var tables = await _client.GetQueryApi().QueryAsync(query, _org);
            var fieldList = tables.SelectMany(table => table.Records).Select(o => o.GetField()).Distinct();
            var eChartsData = new EChartsData
            {
                DeviceName = option.DeviceName,
                FieldDataList = new List<FieldData>()
            };
            var fluxRecords = tables.SelectMany(table => table.Records);

            foreach (var field in fieldList)
            {
                eChartsData.FieldDataList.Add(new FieldData
                {
                    FieldName = field,
                    DateTimes = fluxRecords.Where(o => o.GetField()== field).Select(o => o.GetTime().Value.ToDateTimeUtc())
                        .ToList(),
                    Values = fluxRecords.Where(o => o.GetField() == field).Select(o => (double)o.GetValue()).ToList(),
                });
            }

            return eChartsData;

        }

这里查询方法与写入类似,通过SDK提供的_client.GetQueryApi()方法获取查询api,然后通过QueryAsync方法查询我们拼凑的语句。查询的结果是tables集合,我们可以通过GetTime()和GetValue()方法来拿到时间和对应的值。
由于influxdb存储的时间都是UTC时间,所以查询条件需要转换成UTC时间,使用o.GetValue()获取到的是object类型,我们需要转换成double。

添加ECharts图表

接下来我们开始在UI项目中添加ECharts图表,第一步先在MASA.IoT.UI项目的_Host.cshtml文件中部分添加echarts的js文件

<script src="https://cdn.masastack.com/npm/echarts/5.1.1/echarts.min.js"></script>

使用MASA Blazor创建页面就相对简单很多了,首先有一个设备列表页面,展示设备的名称和在线状态,当点击设备右侧的按钮时,弹出抽屉页面,显示我们的ECharts图表,这里还使用了Tab组件,方便以后扩展设备相关其他功能

@page "/DeviceList"
@using MASA.IoT.Core.Contract.Device
@using MASA.IoT.UI.Components
<PageTitle>设备列表</PageTitle>
<h1>设备列表</h1>
<MDataTable Headers="_headers" Items="deviceList" Class="elevation-1" Page="_options.PageIndex" ItemsPerPage="_options.PageSize"
            ServerItemsLength="_totalCount">
    <ItemColContent>
        @if (context.Header.Value == "actions")
        {
            <MIcon Small Class="mr-2" OnClick="()=>EditItem(context.Item)">mdi-pencil</MIcon>
        }
        else if (context.Header.Value == nameof(DeviceListViewModel.OnLineStates))
        {
            <EnumChip Value="context.Item.OnLineStates"></EnumChip>
        }
        else
        {
            @context.Value
        }
    </ItemColContent>
</MDataTable>
<PDrawer Width="1000" Value="ShowDrawer" ValueChanged="DrawerChangedAsync">
    <ActivatorContent>

    </ActivatorContent>
    <ChildContent>
        <MTabs ValueChanged="TabsValueChanged">
            <MTab>图表</MTab>
        </MTabs>
        <MTabsItems @bind-Value="_tabIndex">
            <MTabItem>
                <MCard Flat>
                    <MECharts Class="rounded-3" Option="_optionECharts" Height="350"></MECharts>
                </MCard>
            </MTabItem>
        </MTabsItems>
    </ChildContent>
</PDrawer>

页面逻辑代码如下:

using BlazorComponent;
using MASA.IoT.Common.Helper;
using MASA.IoT.Core.Contract.Device;
using MASA.IoT.UI.Caller;
using Microsoft.AspNetCore.Components;

namespace MASA.IoT.UI.Pages
{
    public partial class DeviceList : ComponentBase
    {
        StringNumber _tabIndex;
        private object _optionECharts = new();

        private int _totalCount = 0;
        private MqttHelper mqttHelper { get; set; }
        private List<DeviceListViewModel> deviceList { get; set; } = new();
        private bool ShowDrawer { get; set; }
        [Inject]
        private DeviceCaller _deviceCaller { get; set; }

        private readonly DeviceListOption _options = new()
        {
            PageIndex = 1,
            PageSize = 10,
        };

        private List<DataTableHeader<DeviceListViewModel>> _headers = new()
        {
           new DataTableHeader<DeviceListViewModel>
           {
                Text= "设备名称",
                Align= DataTableHeaderAlign.Start,
                Sortable= false,
                Value= nameof(DeviceListViewModel.DeviceName)
           },
          new DataTableHeader<DeviceListViewModel>
           {
              Text= "在线状态",
              Align= DataTableHeaderAlign.Start,
              Sortable= false,
              Value= nameof(DeviceListViewModel.OnLineStates)
           },
           new DataTableHeader<DeviceListViewModel>
           {
               Text= "Actions", 
               Value= "actions",
               Sortable=false,
               Width="100px",
               Align=DataTableHeaderAlign.Center,
           }
         };

        private async Task DrawerChangedAsync()
        {
            ShowDrawer = !ShowDrawer;
        }

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                var paginatedList = await _deviceCaller.DeviceListAsync(new DeviceListOption { PageIndex = 1, PageSize = 10, ProductId = new Guid("C85EF7E5-2E43-4BD2-A939-07FE5EA3F459") });
                deviceList = paginatedList.Result.ToList();
                _totalCount = (int)paginatedList.Total;
                StateHasChanged();
            }
            await base.OnAfterRenderAsync(firstRender);
        }

        private async Task EditItem(DeviceListViewModel item)
        {
            var eChartsData = await _deviceCaller.GetDeviceDataPointList(new GetDeviceDataPointListOption { ProductId = Guid.Parse("c85ef7e5-2e43-4bd2-a939-07fe5ea3f459"), DeviceName = item.DeviceName, StartDateTime = DateTime.Today, StopDateTime = DateTime.Today.AddDays(1) });
            if (eChartsData != null)
            {
                _optionECharts = GetEChartsData(eChartsData);
            }
            ShowDrawer = true;
        }

        private async Task TabsValueChanged(StringNumber value)
        {
            _tabIndex = value;
        }

        private dynamic GetEChartsData(EChartsData data)
        {
            return new
            {
                tooltip = new
                {
                    trigger = "axis"
                },
                legend = new
                {
                    Data = new[] { "Pm2.5", "Humidity", "Temperature" }
                },
                XAxis = new
                {
                    Type = "category",
                    Data = data.FieldDataList.First().DateTimes.Select(o => o.ToLocalTime().ToString("t"))
                },
                YAxis = new
                {
                    Min = 10,
                    Max = 100,
                    Type = "value",
                },
                Series = new[]
                {
                new
                {
                    Name ="Pm2.5",
                    Type = "line",
                    Smooth = true,
                    Data = data.FieldDataList.First(o => o.FieldName=="PM_25").Values
                },
                new
                {
                    Name ="Humidity",
                    Type = "line",
                    Smooth = true,
                    Data = data.FieldDataList.First(o => o.FieldName=="Humidity").Values
                },
                new
                {
                    Name ="Temperature",
                    Type = "line",
                    Smooth = true,
                    Data = data.FieldDataList.First(o => o.FieldName=="Temperature").Values
                }
            }
            };
        }
    }
}

这里查询当天数据(StartDateTime = DateTime.Today, StopDateTime = DateTime.Today.AddDays(1)),GetEChartsData方法返回匿名对象用于ECharts展示,其他内容相对简单不过多赘述。

效果

效果如下

总结

influxdb的自带统计函数很多,可以满足业务上的绝大多数需求,而且还可以自定义函数,结合MASA Blazor和ECharts可以轻松打造丰富直观的Dashboard。另外Influxdb的UI界面也支持定义Dashboard,目前支持八种图表展示。
完整代码在这里:https://github.com/sunday866/MASA.IoT-Training-Demos

标签:4.4,MASA,DeviceName,IoT,private,查询,new
From: https://www.cnblogs.com/sunday866/p/17561950.html

相关文章

  • 4.4列表练习题平方和
      ......
  • 4.4列表的简单统计函数
      ......
  • 如何使用 Amazon Systems Manager 集中管理 Amazon IoT Greengrass 设备
    对于边缘设备管理员来说,远程管理大量不同的系统和应用程序会是一项富有挑战性的任务。AmazonIoTGreengrass 可帮助这些系统管理员管理其边缘设备应用程序堆栈。不过,这些设备上的系统软件必须通过与其大型IT企业的运营策略一致的运营策略来单独更新和维护。此外,客户必须构建或......
  • IoTOS-v1.5.3 新增 智能诊断&会话记录导出
    IoTOS v1.5.3     一、新增智能诊断       智能诊断功能:    智能诊断会根据不同上游接口能力开放提供接近官方甚至比官方更加完善的智能诊断功能。    目前还原OneLink官方智能诊断功能包括动效、诊断建议等可供诊断的接口基本全部覆盖;(卡状态、......
  • 华普物联RS485/RS232双串口服务器 转以太网串口 RJ45 河南华普 HPIOT
    一款工业级串口服务器,实现了RJ45网口与RS485或RS232之间的数据透明传输;支持Modbus网关功能;支持多种保活机制;支持注册包+双向心跳包、虚拟串口、自动重连等功能。公司介绍华普物联科技产品包括物联网网关、工业无线路由器、LoRa基站、DTU、RTU、远程IO等产品,以及支持边缘计算......
  • 华普物联 RS485转以太网IO控制器 河南华普 HPIOT
    支持8路继电器输出、8路输入,支持50CKET连接远程服务器;支持WEB网页设置设备参数,支持主动上报功能,支持两种工作模式:主机模式、从机模式,主机模式支持RS485级联多个MODBUSRTU设备,支持MODBUSTCP/RTU协议自适应,支持连接华普华普云支持8个条件控制指令控制更加便捷,支持硬件看门狗。......
  • 华普物联 CAT 1/4G网络IO控制器 河南华普 HPIOT
    支持8路继电器输出、8路输入;支持50CKET支持两种工作模式:主机模式、从机模式,主机模式,支持RS485级联多个MODBUSRTU设备,支持连接华普云;支持硬件看门狗,电源具有良好的过流、过压、防反接保护等功能。公司介绍华普物联科技产品包括物联网网关、工业无线路由器、LoRa基站、......
  • 华普物联两路IO串口继电器 MODBUS IO 河南华普 HPIOT
    采用标准Modbus-RTU协议的继电器设备支持RS485。支持宽电压供电,支持2路DI输入、2路DO输出、2路模拟量输入,可广泛应用于工业生产、农业、智慧城市、写字楼等各类应用场景。公司介绍华普物联科技产品包括物联网网关、工业无线路由器、LoRa基站、DTU、RTU、远程IO等产品,以......
  • 华普物联16路IO串口继电器 MODBUS IO 河南华普 HPIOT
    是采用标准Modbus-RTU协议的继电器设备,支持RS485。支持宽电压供电,16路输出控制支持硬件和软件设置从机地址,可广泛应用于工业生产、农业、智慧城市、写字楼等各类应用场景。公司介绍华普物联科技产品包括物联网网关、工业无线路由器、LoRa基站、DTU、RTU、远程IO等产品......
  • 华普物联4G DTU RS232/RS485转4G串口服务器 河南华普 HPIOT
    采用工业级设计标准,内置独立硬件看门狗,RS485完全独立电器隔离,支持多路SOCKET,同时可连接四个服务器,支持自定义心跳包/SN心跳包/1CCID心跳包/ME心跳包,支持自定义注册包/SN注册包/ICCID注册包/IMEI注册包。公司介绍华普物联科技产品包括物联网网关、工业无线路由器、LoRa基站......