6.1 时序数据存储
时序数据库简介
时序数据库(Time Series Database,简称 TSDB)是一种专门为处理时间序列数据而设计的数据库系统。时间序列数据是指随时间变化而连续采集的数据,比如传感器读数、服务器性能监控、IoT 设备数据等。每条数据通常由一个时间戳和一个或多个数值字段组成。
主要特点
- 高写入性能:通常设计为高吞吐量写入,可支持每秒百万级别的数据点写入。
- 按时间索引:所有数据以时间戳排序,便于快速执行基于时间范围的查询。
- 数据压缩与归档:提供高效压缩算法和数据降采样(downsampling),减少存储空间。
- 流式计算:支持实时计算、告警和规则引擎,对数据进行实时分析处理。
- 标签(Tags)系统:数据通过标签进行组织,有利于多维度分析(类似于列式数据库中的维度字段)。
典型应用场景
- 服务器监控
- CPU、内存、磁盘等指标采集与告警。
- 物联网(IoT)
- 采集传感器数据,例如温湿度、电压、电流等。
- 工业控制
- PLC 采集数据实时记录与分析。
- 日志分析
- 将日志事件按时间聚合处理。
SDK简介
BK系列控制器内置了时序数据库,为常见场景进行设备点位的数据存储提供了简便接口,开发人员无需关心数据库维护相关问题,只专注于业务逻辑存储即可,大大节省了项目开发时间及精力。时序数据库存储SDK基于C++11标准。
SDK文件结构
- lib:开发依赖的动态库
- include:开发依赖的头文件
- test:测试文件
TStore API说明
TStore
为数据库操作类,可以通过此类获取数据库的使用信息。
GetTotalSpace
功能
获取数据库总空间大小
函数原型
参数
无
返回值
- 成功:空间大小
- 失败:-1
GetUsedSpace
功能
获取数据库已使用空间大小
函数原型
参数
无
返回值
- 成功:空间大小
- 失败:-1
TSDevTab API说明
TSDevTab
为数据表操作类,一个数据表对应着一个点位采集存储设备。
TSDevTab
功能
数据表构造函数
函数原型
参数
参数名 | 类型 | 说明 |
---|---|---|
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
功能
立刻刷新缓存中的数据到时序数据库中
函数原型
参数
无
返回值
- 成功:0
- 失败:错误码
SDK使用说明
时序数据库存储的核心是 TsdbDevice
,此类将数据存储抽象为以设备为载体的标准化接口,开发人员只需基于原始设备点表做基础配置,然后对设备数据进行读写操作即可,其它逻辑无需关心。
数据存储基础使用说明
以下以存储一个PCS设备为例,对整个存储SDK的调用开发做简单举例。
1、实例化设备类
首先,需要实例化一个 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
函数。
至此,一个基础设备的创建到数据存储就完成了,以下是整个示列的完整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 | 写入点位点位类型异常 |