跳转至

6.1 时序数据存储

时序数据库简介

时序数据库(Time Series Database,简称 TSDB)是一种专门为处理时间序列数据而设计的数据库系统。时间序列数据是指随时间变化而连续采集的数据,比如传感器读数、服务器性能监控、IoT 设备数据等。每条数据通常由一个时间戳和一个或多个数值字段组成。

主要特点

  1. 高写入性能:通常设计为高吞吐量写入,可支持每秒百万级别的数据点写入。
  2. 按时间索引:所有数据以时间戳排序,便于快速执行基于时间范围的查询。
  3. 数据压缩与归档:提供高效压缩算法和数据降采样(downsampling),减少存储空间。
  4. 流式计算:支持实时计算、告警和规则引擎,对数据进行实时分析处理。
  5. 标签(Tags)系统:数据通过标签进行组织,有利于多维度分析(类似于列式数据库中的维度字段)。

典型应用场景

  1. 服务器监控
  2. CPU、内存、磁盘等指标采集与告警。
  3. 物联网(IoT)
  4. 采集传感器数据,例如温湿度、电压、电流等。
  5. 工业控制
  6. PLC 采集数据实时记录与分析。
  7. 日志分析
  8. 将日志事件按时间聚合处理。

SDK简介

BK系列控制器内置了时序数据库,为常见场景进行设备点位的数据存储提供了简便接口,开发人员无需关心数据库维护相关问题,只专注于业务逻辑存储即可,大大节省了项目开发时间及精力。时序数据库存储SDK基于C++11标准。

SDK文件结构

  • lib:开发依赖的动态库
  • include:开发依赖的头文件
  • test:测试文件

TStore API说明

TStore为数据库操作类,可以通过此类获取数据库的使用信息。

GetTotalSpace

功能

获取数据库总空间大小

函数原型

/**
 * @brief  获取数据库总空间
 * @return  返回总空间大小,单位KB
 *
 */
float GetTotalSpace();

参数

返回值

  • 成功:空间大小
  • 失败:-1

GetUsedSpace

功能

获取数据库已使用空间大小

函数原型

/**
 * @brief  获取数据库已使用空间
 * @return  返回已使用空间大小,单位KB
 *
 */
float GetUsedSpace();

参数

返回值

  • 成功:空间大小
  • 失败:-1

TSDevTab API说明

TSDevTab为数据表操作类,一个数据表对应着一个点位采集存储设备。

TSDevTab

功能

数据表构造函数

函数原型

/**
 * @brief   构造函数
 * @param  name  设备表唯一名称
 */
TSDevTab(std::string name);

参数

参数名 类型 说明
name string 设备表唯一标识

返回值

  • 成功:0
  • 失败:错误码

AddPoint

功能

增加设备点位

函数原型

/**
 * @brief   增加点位
 * @param  config  点位配置对象
 * @return
 *      @retval  0    成功
 *      @retval  非0  错误码
 */
 int AddPoint(const Point &point);

参数

参数名 类型 说明
point Point 设备点位信息

返回值

  • 成功:0
  • 失败:错误码

PointBind

功能

设备点位绑定具体的配置参数

函数原型

/**
* @brief   点位绑定配置信息
* @param  addr     点位地址
* @param  config  点位配置对象
* @return
*      @retval  0    成功
*      @retval  非0  错误码
*/
int PointBind(int addr, const PointConfig &config)

参数

参数名 类型 说明
addr int 设备点位地址
config PointConfig 设备点位配置信息

返回值

  • 成功:0
  • 失败:错误码

Write

功能

写入设备对应点位的值

函数原型

/**
 * @brief 写入点位值
 * @details 调用此函数后并不会立刻将值存入到数据库中,SDK会根据点位阈值比较及周期存储时间比较向数据库中存储数据,如果需要立刻存储当前数据,可以主动调用  Flush()  函数
 * @param addr     点位地址
 * @param value    点位值
 * @param mask     掩码
 * @return
 *      @retval  0    成功
 *      @retval  非0  错误码
 */
 int Write(int addr, UVariant value, unsigned int mask = 0xFFFFFFFF));

参数

参数名 类型 说明
addr int 设备点位地址
value UVariant 设备点位原始值
mask unsigned int 设备点位值对应掩码

返回值

  • 成功:0
  • 失败:错误码

WriteForce

功能

强制写入设备对应点位的值

函数原型

/**
 * @brief 强制写入点位值
 * @param addr     点位地址
 * @param value    点位值
 * @param mask     掩码
 * @return
 *      @retval  0    成功
 *      @retval  非0  错误码
 */
 int Write(int addr, UVariant value, unsigned int mask = 0xFFFFFFFF));

参数

参数名 类型 说明
addr int 设备点位地址
value UVariant 设备点位原始值
mask unsigned int 设备点位值对应掩码

返回值

  • 成功:0
  • 失败:错误码

Read

功能

读取当前点位的值

函数原型

/**
 * @brief 读取点位值
 * @param addr     点位地址
 * @param mask     掩码
 * @return
 *      @retval 成功: 值对应指针
 *      @retval 失败: 空指针
 */
 ConvertedValue_U *Read(int addr, unsigned int mask = 0xFFFFFFFF);

参数

参数名 类型 说明
addr int 设备点位地址
mask unsigned int 设备点位值对应掩码

返回值

  • 成功:值对应指针
  • 失败:空指针

Flush

功能

立刻刷新缓存中的数据到时序数据库中

函数原型

/**
 * @brief 将当前设备点位变更值立刻存入数据库中
 * @return
 *      @retval  0    成功
 *      @retval  非0  错误码
 */
int Flush();

参数

返回值

  • 成功:0
  • 失败:错误码

SDK使用说明

时序数据库存储的核心是 TsdbDevice,此类将数据存储抽象为以设备为载体的标准化接口,开发人员只需基于原始设备点表做基础配置,然后对设备数据进行读写操作即可,其它逻辑无需关心。

数据存储基础使用说明

以下以存储一个PCS设备为例,对整个存储SDK的调用开发做简单举例。

1、实例化设备类

首先,需要实例化一个 TsdbDevice作为PCS设备点位的载体

TsdbDevice pcs;

2、配置设备点位

设备点位配置分为两步:基础点位添加和绑定点位配置。如有下面表格所示的PCS Modbus通讯说明文档

地址 名称 权限 数据类型 系数 单位 备注
0x0001 PCS 端口 A 相电压 只读 U16 0.1 V
0x0002 PCS 端口 B 相电压 只读 U16 0.1 V
0x0003 PCS 端口 C 相电压 只读 U16 0.1 V
0x0004 PCS 输出 A 相电流 只读 S16 0.1 A
0x0005 PCS 输出 B 相电流 只读 S16 0.1 A
0x0006 PCS 输出 C 相电流 只读 S16 0.1 A

添加基础点位如下:

//根据实际设备点位配置点位信息
pcs.AddPoint({0x0001, 0xFFFFFFFF, "PCS端口A相电压", TsdbDevice::UVariant{.as_u16 = 0}, false});
pcs.AddPoint({0x0002, 0xFFFFFFFF, "PCS端口B相电压", TsdbDevice::UVariant{.as_u16 = 0}, false});
pcs.AddPoint({0x0003, 0xFFFFFFFF, "PCS端口C相电压", TsdbDevice::UVariant{.as_u16 = 0}, false});
pcs.AddPoint({0x0004, 0xFFFFFFFF, "PCS端口A相电流", TsdbDevice::UVariant{.as_u16 = 0}, false});
pcs.AddPoint({0x0005, 0xFFFFFFFF, "PCS端口B相电流", TsdbDevice::UVariant{.as_u16 = 0}, false});
pcs.AddPoint({0x0006, 0xFFFFFFFF, "PCS端口C相电流", TsdbDevice::UVariant{.as_u16 = 0}, false});

绑定对应的点位配置信息,点位配置信息是对同类点位配置的抽象,如上表格所示的点位,虽然有6个点位,但只需2个点位配置,即:电压点位配置、电流点位配置,这样可以避免为每个点位都附属更多属性。

// 设置电压点位配置
TSDevTab::PointConfig voltageConf;
voltageConf.unit = "V";
voltageConf.p_type = TSDevTab::kAnalog;
voltageConf.v_type = TSDevTab::kU16;
voltageConf.coefficient = 0.1f;
voltageConf.threshold.as_u16 = 300; // 例如阀值为 30.0V(原始值 300)
voltageConf.interval = 2000;        // 每 2 秒存储一次

// 将点位与对应设置绑定
pcs.PointBind(0x0001, voltageConf);
pcs.PointBind(0x0002, voltageConf);
pcs.PointBind(0x0003, voltageConf);

// 设置电流点位配置
TSDevTab::PointConfig currentConf;
currentConf.unit = "A";
currentConf.p_type = TSDevTab::kAnalog;
currentConf.v_type = TSDevTab::kU16;
currentConf.coefficient = 1.0f;
currentConf.threshold.as_u16 = 30; // 例如阀值为 30A
currentConf.interval = 5000;       // 每 5 秒存储一次

// 将点位与对应设置绑定
pcs.PointBind(0x0004, currentConf);
pcs.PointBind(0x0005, currentConf);
pcs.PointBind(0x0006, currentConf);

3、数据更新

当设备基础点位配置完成后,即可通过相关通讯协议,将采集到的数据写入到时序数据库中

// 模拟读取通过各类数据协议读到值后进行写入
int loop_cout = 10;
while (loop_cout-- < 0)
{
    for (int i = 1; i < 7; i++)
    {
        pcs.Write(i, TSDevTab::Variant_u{.as_u16 = 0});
     }
     std::this_thread::sleep_for(std::chrono::seconds(3));
}

需要注意的是,写入数据后,实时数据可能并不会立刻存入到数据库中,写入时机会受到点位配置中阀值和存储时间两项设定的影响,您可参考下一节关于配置的详细说明。

如果需要立刻执行数据库的写入,可以主动调用 Flush函数。

// 程序结束前刷新数据做保存
pcs.Flush();

至此,一个基础设备的创建到数据存储就完成了,以下是整个示列的完整Demo代码

#include "tsdb_device.h"  //引入头文件
#include <thread>
#include <chrono>

int main()
{
    // 实例化pcs设备
    TSDevTab pcs("pcs");
    // 根据实际设备点位配置点位信息
    pcs.AddPoint({0x0001, 0xFFFFFFFF, "PCS端口A相电压", TSDevTab::Variant_u{.as_u16 = 0}, false});
    pcs.AddPoint({0x0002, 0xFFFFFFFF, "PCS端口B相电压", TSDevTab::Variant_u{.as_u16 = 0}, false});
    pcs.AddPoint({0x0003, 0xFFFFFFFF, "PCS端口C相电压", TSDevTab::Variant_u{.as_u16 = 0}, false});
    pcs.AddPoint({0x0004, 0xFFFFFFFF, "PCS端口A相电流", TSDevTab::Variant_u{.as_u16 = 0}, false});
    pcs.AddPoint({0x0005, 0xFFFFFFFF, "PCS端口B相电流", TSDevTab::Variant_u{.as_u16 = 0}, false});
    pcs.AddPoint({0x0006, 0xFFFFFFFF, "PCS端口C相电流", TSDevTab::Variant_u{.as_u16 = 0}, false});

    // 设置电压点位配置
    TSDevTab::PointConfig voltageConf;
    voltageConf.unit = "V";
    voltageConf.p_type = TSDevTab::kAnalog;
    voltageConf.v_type = TSDevTab::kU16;
    voltageConf.coefficient = 0.1f;
    voltageConf.threshold.as_u16 = 300; // 例如阀值为 30.0V(原始值 300)
    voltageConf.interval = 2000;        // 每 2 秒存储一次

    // 将点位与对应设置绑定
    pcs.PointBind(0x0001, voltageConf);
    pcs.PointBind(0x0002, voltageConf);
    pcs.PointBind(0x0003, voltageConf);

    // 设置电流点位配置
    TSDevTab::PointConfig currentConf;
    currentConf.unit = "A";
    currentConf.p_type = TSDevTab::kAnalog;
    currentConf.v_type = TSDevTab::kU16;
    currentConf.coefficient = 1.0f;
    currentConf.threshold.as_u16 = 30; // 例如阀值为 30A
    currentConf.interval = 5000;       // 每 5 秒存储一次

    // 将点位与对应设置绑定
    pcs.PointBind(0x0004, currentConf);
    pcs.PointBind(0x0005, currentConf);
    pcs.PointBind(0x0006, currentConf);

    // 模拟读取通过各类数据协议读到值后进行写入
    int loop_cout = 10;
    while (loop_cout-- < 0)
    {
        for (int i = 1; i < 7; i++)
        {
            //写入数据
            pcs.Write(i, TSDevTab::Variant_u{.as_u16 = 0});
        }
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }
    // 程序结束前刷新数据做保存
    pcs.Flush();
    return 0;
}

相关数据信息,可以通过运维平台进行查看。

点位配置数据结构说明

从上节的示例中可以看到,点位需要绑定特定的点位配置信息,点位配置信息会影响到点位值的写入时机、点位显示等,点位配置数据结构如下:

// 点位配置信息
struct PointConfig
{
    std::string unit;    // 点位单位
    PointType_E p_type;  // 点位类型
    ValueType_E v_type;  // 点位值类型
    float coefficient;   // 点位系数
    Variant_u threshold; // 点位阀值
    int interval;        // 点位循环存储周期,毫秒值
};

其属性说明如下:

  • unit:点位单位,在数据展示时会自动显示
  • p_type:点位类型,枚举值,代表当前点位是开关量还是模拟量,主要影响数据展示方式
  • v_type:点位值类型,枚举值,代表当前点位值是何种数据类型,会影响到数据对比、数据解析、数据展示
  • coefficient:点位系数,通常常见通过各类协议采集到的都是整型值,真实值需要根据对应的系数做处理
  • threshold:点位阀值,对于大部分数据点位,实际的存储需求通常不是实时采集存储,而是当点位值变动波动一定程度后进行存储,可以通过设置此数据,当两次采集值大于阀值是,会将采集数据存储到数据库中,如果您希望只要有变动就进行存储,可以设置此值为0.
  • interval:循环存储时间周期,当点位值变动一直小于阀值设定时,可以设置此值,当两次采集周期超过此值时,也会进行数据存储

错误码说明

错误码 错误说明
10001 sd卡未挂载
10002 数据库连接失败
10003 配置初始化失败
10004 点位添加失败
10005 点位绑定失败
10006 写入点位不存在
10007 写入点位属性不存在
10008 写入点位数据类型异常
10009 写入点位点位类型异常