引言
可再生能源如风力和太阳能发电,具有低成本和环保的特性,是未来能源供应的主要方向。然而,这类发电方式存在供应分散、设备数量多、地区分布广等特点。再加上不同地区的季节和天气变化,不确定性极大。
随着社会用电需求的持续增加,如何合理调配电力成为保障供需平衡和最大化新能源发电效益的关键。
本文将介绍如何采用 EMQX 企业版和 Snowflake,帮助用户在复杂的电力供应链中,实现发电设备数据的采集、存储和分析。通过这一集成,准确预测发电容量,从而实现高效的运营。
场景介绍
太阳能和风力发电量的预测依赖地理位置、历史的气候信息、运行信息系和发电量数据。本文我们使用 MQTT 客户端工具 MQTTX CLI 的 simulate
命令,配合模拟脚本生成多个太阳能和风力发电站 MQTT 状态数据采集上报与客户端(虚拟电站)。
- 虚拟电站将连接到 EMQX 上,周期性生成模拟数据,并向指定 MQTT 主题发布自身状态数据;
- EMQX 在接收到消息后,使用内置的规则引擎和数据集成功能,将其存储到 Snowflake 中;
- Snowflake 保存数据后,在其平台上进行数据分析。
典型的数据格式如下:
字段名 | 数据类型 | 说明 |
---|---|---|
id | STRING | 唯一标识符,用于标识每条数据记录 |
city | STRING | 城市名,用于标识数据的来源城市 |
model | STRING | 设备型号,用于标识数据对应的设备型号 |
regionID | STRING | 区域编号,用于标识设备所在的区域编号 |
type | STRING | 设备类型,值为 “Wind” 或 “Solar” |
ratedPower | FLOAT | 设备的额定功率,单位为千瓦 (kW) |
timestamp | TIMESTAMP | 数据记录的时间戳,表示数据生成的具体时间 |
powerOutput | FLOAT | 实时输出功率,单位为千瓦 (kW) |
windSpeed | FLOAT | 风速,仅对风力发电设备有效,单位为米/秒 (m/s) |
solarRadiation | FLOAT | 光照强度,仅对太阳能发电设备有效,单位为瓦特每平方米 (W/m²) |
rotationSpeed | FLOAT | 转速,仅对风力发电设备有效,单位为每分钟转数 (RPM) |
对应的数据示例如下:
{
"id": "6b50f69c-9c9b-48e7-ae9d-849e6e5e5dd5",
"city": "San Francisco",
"model": "Solar-Model-A1",
"regionID": "01",
"type": "Solar",
"ratedPower": 15.5,
"timestamp": "2024-07-10T12:34:56Z",
"powerOutput": 12.3,
"windSpeed": null,
"solarRadiation": 720,
"rotationSpeed": null
}
安装 EMQX 企业版
EMQX 企业版是一款企业级 MQTT 物联网接入平台,能够提供高可靠、高性能的物联网实时数据接入,并实现数据的处理和集成。
请参照此处安装 EMQX 企业版。
准备 MQTTX 模拟数据
MQTTX CLI 是一款强大而易用的 MQTT 5.0 命令行工具,它提供了 simulate
命令,可以使用 Node.js 编写模拟脚本,实现预期的模拟消息生成与发布。
- 创建一个名为
solar-wind-power-plant.js
文件,将本章节提供的模拟脚本粘贴进去。您也可以参照此处对脚本内容进行修改; - 使用
simulate
运行脚本,指定脚本路径和模拟的客户端数量:
mqttx simulate --file ./solar-wind-power-plant.js -c 10
该命令的含义如下:
--file
选项指定运行./solar-wind-power-plant.js
脚本文件-c
选项指定模拟客户端数量为 10 个
您可以根据自己需要,按照MQTTX CLI 发布命令选项 调整客户端数量和消息发布频率。
执行命令后,脚本将建立 10 个客户端连接到 EMQX,并根据场景中定义的数据类型,每个客户端每秒向 mqttx/simulate/Solar-Wind-Power-Plant/{clientid}
主题发布一条消息。
您可以使用 MQTTX CLI 的 sub
命令订阅主题,验证消息是否正常发布:
mqttx sub -t mqttx/simulate/Solar-Wind-Power-Plant/+ -v
附录:模拟脚本内容。
const store = {
index: 0
};
function transformToFloat(val) {
if (typeof val !== 'number') {
val = Number(val);
}
const _val = val.toFixed(2);
if (_val.endsWith('.00')) {
return parseFloat(_val) + 0.01;
}
return parseFloat(_val);
}
function getWindPower(hour, faker) {
if (hour >= 8 && hour < 18) {
return faker.datatype.float({
min: 900, max: 1100 });
} else {
return faker.datatype.float({
min: 600, max: 900 });
}
}
function calculateWindSpeed(rotationSpeed) {
// 假设转速和风速之间的线性关系
return rotationSpeed / 60; // 简单的线性关系
}
function getSolarPower(hour, isCloudy