Compare commits

...

98 Commits

Author SHA1 Message Date
7153c00d0c 重构 2026-04-16 23:11:25 +08:00
2b3be8636f 1.单体电池批量新增修改 2026-04-16 19:49:56 +08:00
5eb9b455a5 1.单体电池批量新增修改 2026-04-15 22:47:17 +08:00
4947f085c7 增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改 2026-04-14 16:44:31 +08:00
5460aadf6c 增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改 2026-04-14 16:11:23 +08:00
79a8040446 1.一周充放曲线改为了时间聚合柱状图。
2.PCS最高温度修复bug展示多PCS设备的数据
3.PCS的状态根据状态枚举映射配置的内容显示
4.BMS的总览,增加工作状态、与PCS通讯、与EMS通讯的配置,及关联展示
5.增加批量导入单体电池点位的功能
6.修复计算点可能会出现id与code在一个池子内的问题,再次计算后数据正常
7.计算点增加小数位限制的功能,实时计算与7天历史接口都已经按照配置的小数位进行限制
8.统计报表中的功率曲线改为了按照分钟显示
9.功率曲线出现断点的问题是因为数据计算太密集了导致的,增加了前端连线不断的显示
10.PCS和电池堆的曲线与配置增加了关联设备显示
11.点位映射中的电池温度,增加了多设备
12.收益报表增加升序排列,合并当月所有合计
13.增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改
2026-04-12 15:16:57 +08:00
ed36292c94 Merge pull request 'dev' (#3) from dev into waibao
Reviewed-on: #3
2026-04-09 01:31:05 +00:00
515f440298 重构 2026-03-23 13:47:20 +08:00
e4cfd15cb4 重构 2026-03-18 10:06:42 +08:00
5ab2cb8f90 重构 2026-02-22 18:59:40 +08:00
4e7d387edf 重构 2026-02-17 21:42:59 +08:00
49ed5f218a 重构 2026-02-16 14:46:27 +08:00
8806473080 重构 2026-02-16 13:41:35 +08:00
71d0b0f609 重构 2026-02-15 16:02:06 +08:00
6253fb6b2d 临时修改 2026-02-13 21:41:23 +08:00
21673ecd1e 临时修改 2026-02-12 21:07:41 +08:00
66de6fe77c Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java
#	ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java
2026-02-12 21:06:36 +08:00
63ed2641ee 临时修改 2026-02-12 21:05:11 +08:00
5d5a7137fc Merge pull request 'dev' (#2) from dev into main
Reviewed-on: #2
2026-02-11 01:55:45 +00:00
zq
6545b4f947 修改 2026-01-28 19:26:15 +08:00
zq
8a44009c42 修改设备监控电表类型设备曲线不展示问题 2026-01-28 19:21:30 +08:00
zq
2b22b70baa 新增电表报表、收益报表导出功能 2026-01-28 15:59:30 +08:00
zq
5a86769b40 统计报表-功率曲线按照分钟展示数据 2026-01-27 19:15:55 +08:00
zq
eeccd19f0a 设备监控页面-设备告警铃铛数由查询告警点位数据改为查询设备故障告警 2026-01-27 17:54:11 +08:00
zq
d19f07c4e8 modbus本地设备数据读取失败,告警等级修改为“一般”,读到数据后自恢复 2026-01-27 17:37:42 +08:00
zq
9f2c303047 topic设备数据消息5次没有数据内容则增加告警,告警等级修改为“一般”,读到数据后自恢复 2026-01-27 17:26:11 +08:00
zq
7199677d7b 解决null值转换为字符串 "null"问题 2026-01-27 17:23:32 +08:00
zq
4b58838d5d 解决null值转换为字符串 "null"问题 2026-01-27 16:55:04 +08:00
zq
14904ea6b2 站点首页-设备告警仅存在严重、紧急告警时提醒 2026-01-27 16:49:20 +08:00
zq
5225f03195 站点首页-当日功率曲线按照分钟展示数据 2026-01-27 16:47:48 +08:00
zq
619c05e965 修改消防数据查询报错问题 2026-01-26 16:46:31 +08:00
zq
1531af2969 修改消防数据查询报错问题 2026-01-26 14:38:16 +08:00
e55cdc6504 临时修改 2026-01-25 18:47:38 +08:00
400ae98b4e 临时修改 2026-01-25 14:00:18 +08:00
zq
dd060b0abf 单体电池解析数据改动;枚举值匹配转换改动 2026-01-24 20:51:16 +08:00
6ae9126662 回滚到 19:29 2026-01-24 00:20:27 +08:00
83fae710fb 修改小错误 2026-01-23 23:46:46 +08:00
c518b9bcae mqtt转移到测试环境,生产由电动所的应用提供 mqtt 数据 2026-01-23 23:09:31 +08:00
034c682864 batteryDataProcessFromBmsc方法device_id改成 device_id+cluster_id 2026-01-23 21:05:07 +08:00
5f7604d36b 加了日志,device_id改成 device_id+cluster_id 2026-01-23 20:16:05 +08:00
zq
084201bb58 修改 2026-01-23 19:43:48 +08:00
zq
194429af2d 修改 2026-01-23 19:29:24 +08:00
zq
b3f7ca7a81 设备监控-实时运行页面曲线按照每分钟展示 2026-01-23 19:11:13 +08:00
zq
5eccb35568 修改 2026-01-23 17:38:26 +08:00
zq
c007016224 告警消息按时间倒序查询 2026-01-23 15:30:07 +08:00
zq
768dd0bead 电表设备详情其他类型展示值查询 2026-01-23 15:29:44 +08:00
zq
26c534cae9 修改 2026-01-23 11:20:05 +08:00
zq
24ba30a536 逆变器功率上限值,默认为100kW 2026-01-23 10:40:26 +08:00
zq
bb85ca1d5e 修改 2026-01-22 18:12:40 +08:00
zq
ff76a73a61 修改 2026-01-22 18:03:47 +08:00
zq
1db167f0f8 Merge branch 'dev' of http://101.43.41.9:13000/xzzn/emsback into dev 2026-01-22 17:53:43 +08:00
f592b709a7 解决报错问题 2026-01-22 16:06:52 +08:00
zq
7f025be41b 站点首页新增展示字段:昨日充电量、昨日放电量、昨日实时收入 2026-01-22 15:40:29 +08:00
8968dc7a41 Merge remote-tracking branch 'origin/dev' into dev 2026-01-22 15:08:50 +08:00
46ea4acc2c 设备列表不展示display_flg=1的数据 2026-01-22 15:08:39 +08:00
zq
04bf9fe1c1 收益报表新增一列”实际收益“,价格保留三位小数 2026-01-22 14:30:52 +08:00
zq
b4f867f796 解决查询空指针报错问题 2026-01-22 14:17:07 +08:00
zq
ecc4799afd 修改查询策略语句 2026-01-22 09:47:47 +08:00
3287cddc6a 用户信息增加 belongsite 2026-01-21 14:53:21 +08:00
11e217787b 修正 modbus 版本为 3.1.0 2026-01-20 21:58:11 +08:00
fec45dac03 修正 modbus 长连接 2026-01-20 21:22:35 +08:00
939bcbe950 修正 modbus 写入超时问题 2026-01-20 19:44:10 +08:00
12a459854e 修正 modbus 超时问题 2026-01-20 17:21:12 +08:00
a31a1a1caa 修正 modbus 超时问题 2026-01-20 17:19:35 +08:00
zq
8716d43879 修改modbus读取设备数据连接池配置 2026-01-20 10:54:14 +08:00
zq
dd0132ab2f 解决保护方案告警不触发问题 2026-01-16 17:06:31 +08:00
zq
fb64be5a5a PCS关机先向设备发送给定功率值再发送关机指令 2026-01-15 17:49:03 +08:00
zq
0076872134 解决保护方案告警不触发问题 2026-01-15 17:40:51 +08:00
zq
f05ba30f14 修改设备监控-PCS实时有功功率与总交流有功功率不一致问题 2026-01-15 15:21:26 +08:00
zq
9aa7dd9d18 运行策略测试修改 2026-01-15 14:45:28 +08:00
zq
3f4d3772b0 设备告警点位解析修改 2026-01-12 15:18:21 +08:00
zq
685a2e8687 工作状态解析格式修改 2026-01-12 13:26:40 +08:00
zq
ebc06e2a2d 运行策略防逆流逻辑修改 2026-01-12 13:25:34 +08:00
zq
2fe5d7b8a1 获取设备列表参数:站点ID不必传 2026-01-08 17:19:01 +08:00
zq
31fe793e97 开关机功能增加设备参数校验 2026-01-08 17:16:24 +08:00
zq
1eee07e040 设备配置表新增work_status字段 2026-01-08 16:46:24 +08:00
zq
566119f692 设备告警点位数据解析逻辑修改 2026-01-08 15:33:54 +08:00
zq
77a3b6e855 设备在线状态和工作状态取值调整 2026-01-07 11:27:02 +08:00
zq
a4ac11e7d9 解决策略同步数据不对和重复问题 2026-01-06 11:27:20 +08:00
zq
4f3cd8c82b 解决策略同步开始和结束时间格式解析报错 2026-01-06 10:26:39 +08:00
zq
873119a1ff 设备上送数据解析判断设备类型 2026-01-06 10:25:08 +08:00
zq
6ed7a25819 修改切面代理配置 2026-01-05 16:25:46 +08:00
zq
dbdf1a5a61 PCS开关机相关配置不在数据表维护 2026-01-05 14:23:30 +08:00
zq
2278b63ada 运行策略轮询PCS设备配置读取逻辑修改 2026-01-04 11:07:35 +08:00
zq
b032e8e073 运行策略轮询PCS设备配置读取逻辑修改 2026-01-04 11:06:03 +08:00
zq
f24072f248 PCS设备开关机逻辑修改 2026-01-04 10:40:38 +08:00
zq
041c41822e PCS设备配置功能修改 2026-01-04 09:00:10 +08:00
zq
552d471fde 策略下发日志修改 2025-12-30 15:25:59 +08:00
zq
e381ff86c8 设备监控-杭州湾电表数据不显示问题修改 2025-12-30 15:06:42 +08:00
zq
2c9cc1f1d4 运行策略轮询放电逻辑修改 2025-12-30 14:36:11 +08:00
zq
93ed334c0e 新增PCS设备配置功能 2025-12-29 18:07:30 +08:00
zq
bac66aaddb 新增PCS设备配置功能 2025-12-29 17:45:53 +08:00
zq
5456d4742e 新增运行策略轮询功能 2025-12-29 15:47:28 +08:00
zq
22415df926 没有未处理的故障告警,站点首页不显示“设备告警”提示 2025-12-26 10:58:46 +08:00
zq
9e086b77b5 运行策略页面开始和结束时间显示按照升序排序 2025-12-26 10:43:12 +08:00
zq
56613e8f70 新增PCS点位字段配置-电池簇数 2025-12-25 11:42:06 +08:00
zq
18ff0100b9 modbus读取本地设备数据增加告警逻辑 2025-12-24 16:19:04 +08:00
zq
90e9b004b4 统计报表-电表报表:计算本次与上次数据差值,累加到对应的数据类型尖、峰、平、谷里面 2025-12-23 16:12:04 +08:00
199 changed files with 20130 additions and 5106 deletions

View File

@ -55,4 +55,21 @@ public class EmsAlarmRecordsController extends BaseController
} }
} }
/**
* 关闭告警
*/
@PostMapping("/closeAlarm")
public AjaxResult closeAlarm(@RequestBody EmsAlarmRecords emsAlarmRecords)
{
Long id = emsAlarmRecords.getId();
if (id == null) {
return error("告警id不能为空");
}
String result = iEmsAlarmRecordsService.closeAlarm(id, getUserId());
if ("success".equals(result) || "告警已关闭".equals(result)) {
return AjaxResult.success("操作成功");
}
return error(result);
}
} }

View File

@ -0,0 +1,61 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsDailyChargeData;
import com.xzzn.ems.service.IEmsDailyChargeDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 站点管理-充放电修正 Controller
*/
@RestController
@RequestMapping("/ems/dailyChargeData")
public class EmsDailyChargeDataController extends BaseController {
@Autowired
private IEmsDailyChargeDataService emsDailyChargeDataService;
@GetMapping("/list")
public TableDataInfo list(EmsDailyChargeData emsDailyChargeData) {
startPage();
List<EmsDailyChargeData> list = emsDailyChargeDataService.selectEmsDailyChargeDataList(emsDailyChargeData);
return getDataTable(list);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(emsDailyChargeDataService.selectEmsDailyChargeDataById(id));
}
@Log(title = "站点管理-充放电修正", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsDailyChargeData emsDailyChargeData) {
return toAjax(emsDailyChargeDataService.insertEmsDailyChargeData(emsDailyChargeData, getUsername()));
}
@Log(title = "站点管理-充放电修正", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsDailyChargeData emsDailyChargeData) {
return toAjax(emsDailyChargeDataService.updateEmsDailyChargeData(emsDailyChargeData, getUsername()));
}
@Log(title = "站点管理-充放电修正", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(emsDailyChargeDataService.deleteEmsDailyChargeDataByIds(ids));
}
}

View File

@ -0,0 +1,61 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsDailyEnergyData;
import com.xzzn.ems.service.IEmsDailyEnergyDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 站点管理-数据修正 Controller
*/
@RestController
@RequestMapping("/ems/dailyEnergyData")
public class EmsDailyEnergyDataController extends BaseController {
@Autowired
private IEmsDailyEnergyDataService emsDailyEnergyDataService;
@GetMapping("/list")
public TableDataInfo list(EmsDailyEnergyData emsDailyEnergyData) {
startPage();
List<EmsDailyEnergyData> list = emsDailyEnergyDataService.selectEmsDailyEnergyDataList(emsDailyEnergyData);
return getDataTable(list);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(emsDailyEnergyDataService.selectEmsDailyEnergyDataById(id));
}
@Log(title = "站点管理-数据修正", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsDailyEnergyData emsDailyEnergyData) {
return toAjax(emsDailyEnergyDataService.insertEmsDailyEnergyData(emsDailyEnergyData, getUsername()));
}
@Log(title = "站点管理-数据修正", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsDailyEnergyData emsDailyEnergyData) {
return toAjax(emsDailyEnergyDataService.updateEmsDailyEnergyData(emsDailyEnergyData, getUsername()));
}
@Log(title = "站点管理-数据修正", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(emsDailyEnergyDataService.deleteEmsDailyEnergyDataByIds(ids));
}
}

View File

@ -0,0 +1,51 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsPointCalcConfig;
import com.xzzn.ems.service.IEmsPointCalcConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/ems/pointCalcConfig")
public class EmsPointCalcConfigController extends BaseController {
@Autowired
private IEmsPointCalcConfigService pointCalcConfigService;
@GetMapping("/list")
public TableDataInfo list(EmsPointCalcConfig pointCalcConfig) {
startPage();
List<EmsPointCalcConfig> list = pointCalcConfigService.selectPointCalcConfigList(pointCalcConfig);
return getDataTable(list);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(pointCalcConfigService.selectPointCalcConfigById(id));
}
@Log(title = "计算点配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsPointCalcConfig pointCalcConfig) {
return toAjax(pointCalcConfigService.insertPointCalcConfig(pointCalcConfig, getUsername()));
}
@Log(title = "计算点配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsPointCalcConfig pointCalcConfig) {
return toAjax(pointCalcConfigService.updatePointCalcConfig(pointCalcConfig, getUsername()));
}
@Log(title = "计算点配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(pointCalcConfigService.deletePointCalcConfigByIds(ids));
}
}

View File

@ -0,0 +1,103 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsPointConfig;
import com.xzzn.ems.domain.vo.ImportPointTemplateRequest;
import com.xzzn.ems.domain.vo.PointConfigCurveRequest;
import com.xzzn.ems.domain.vo.PointConfigGenerateRecentRequest;
import com.xzzn.ems.domain.vo.PointConfigLatestValueRequest;
import com.xzzn.ems.service.IEmsPointConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/ems/pointConfig")
public class EmsPointConfigController extends BaseController {
@Autowired
private IEmsPointConfigService pointConfigService;
@GetMapping("/list")
public TableDataInfo list(EmsPointConfig pointConfig) {
startPage();
List<EmsPointConfig> list = pointConfigService.selectPointConfigList(pointConfig);
return getDataTable(list);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(pointConfigService.selectPointConfigById(id));
}
@Log(title = "点位配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsPointConfig pointConfig) {
return toAjax(pointConfigService.insertPointConfig(pointConfig, getUsername()));
}
@Log(title = "点位配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsPointConfig pointConfig) {
return toAjax(pointConfigService.updatePointConfig(pointConfig, getUsername()));
}
@Log(title = "点位配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(pointConfigService.deletePointConfigByIds(ids));
}
@Log(title = "点位配置", businessType = BusinessType.IMPORT)
@PostMapping("/importTemplateBySite")
public AjaxResult importTemplateBySite(@Valid @RequestBody ImportPointTemplateRequest request) {
return success(pointConfigService.importTemplateBySite(request, getUsername()));
}
@Log(title = "点位配置", businessType = BusinessType.IMPORT)
@PostMapping("/importCsv")
public AjaxResult importCsv(@RequestParam String siteId,
@RequestParam(required = false) Boolean overwrite,
@RequestParam("file") MultipartFile file) {
return success(pointConfigService.importCsvBySite(siteId, overwrite, file, getUsername()));
}
@GetMapping("/registerAddress")
public AjaxResult getRegisterAddress(@RequestParam String siteId,
@RequestParam String deviceCategory,
@RequestParam String deviceId,
@RequestParam String dataKey) {
return success(pointConfigService.getRegisterAddress(siteId, deviceCategory, deviceId, dataKey));
}
@PostMapping("/latestValues")
public AjaxResult latestValues(@RequestBody PointConfigLatestValueRequest request) {
return success(pointConfigService.getLatestValues(request));
}
@PostMapping("/curve")
public AjaxResult curve(@RequestBody PointConfigCurveRequest request) {
return success(pointConfigService.getCurveData(request));
}
@Log(title = "点位配置", businessType = BusinessType.INSERT)
@PostMapping("/generateRecent7Days")
public AjaxResult generateRecent7Days(@Valid @RequestBody PointConfigGenerateRecentRequest request) {
return success(pointConfigService.generateRecent7DaysData(request));
}
}

View File

@ -7,8 +7,12 @@ import javax.validation.Valid;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.xzzn.common.annotation.Log; import com.xzzn.common.annotation.Log;
@ -19,6 +23,7 @@ import com.xzzn.ems.domain.EmsPointMatch;
import com.xzzn.ems.domain.vo.DevicePointMatchExportVo; import com.xzzn.ems.domain.vo.DevicePointMatchExportVo;
import com.xzzn.ems.domain.vo.DevicePointMatchVo; import com.xzzn.ems.domain.vo.DevicePointMatchVo;
import com.xzzn.ems.domain.vo.ImportPointDataRequest; import com.xzzn.ems.domain.vo.ImportPointDataRequest;
import com.xzzn.ems.domain.vo.ImportPointTemplateRequest;
import com.xzzn.ems.service.IEmsPointMatchService; import com.xzzn.ems.service.IEmsPointMatchService;
import com.xzzn.common.utils.poi.ExcelUtil; import com.xzzn.common.utils.poi.ExcelUtil;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -49,6 +54,58 @@ public class EmsPointMatchController extends BaseController
util.exportExcel(response, list, "点位匹配数据"); util.exportExcel(response, list, "点位匹配数据");
} }
/**
* 查询点位配置列表
*/
@GetMapping("/list")
public com.xzzn.common.core.page.TableDataInfo list(EmsPointMatch emsPointMatch)
{
startPage();
List<EmsPointMatch> list = emsPointMatchService.selectPointMatchConfigList(emsPointMatch);
return getDataTable(list);
}
/**
* 查询点位配置详情
*/
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id)
{
return success(emsPointMatchService.selectPointMatchById(id));
}
/**
* 新增点位配置
*/
@Log(title = "点位配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsPointMatch emsPointMatch)
{
emsPointMatch.setCreateBy(getUsername());
return toAjax(emsPointMatchService.insertPointMatch(emsPointMatch));
}
/**
* 修改点位配置
*/
@Log(title = "点位配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsPointMatch emsPointMatch)
{
emsPointMatch.setUpdateBy(getUsername());
return toAjax(emsPointMatchService.updatePointMatch(emsPointMatch));
}
/**
* 删除点位配置
*/
@Log(title = "点位配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids)
{
return toAjax(emsPointMatchService.deletePointMatchByIds(ids));
}
/** /**
* 上传点位清单 * 上传点位清单
* @param file * @param file
@ -85,4 +142,18 @@ public class EmsPointMatchController extends BaseController
} }
} }
/**
* 根据站点导入模板点位配置
* @param request 请求参数
* @return 导入结果
*/
@PreAuthorize("@ss.hasPermi('system:user:import')")
@Log(title = "点位配置", businessType = BusinessType.IMPORT)
@PostMapping("/importTemplateBySite")
public AjaxResult importTemplateBySite(@Valid @RequestBody ImportPointTemplateRequest request)
{
String message = emsPointMatchService.importTemplateBySite(request, getUsername());
return success(message);
}
} }

View File

@ -1,37 +1,51 @@
package com.xzzn.web.controller.ems; package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.config.RuoYiConfig; import com.xzzn.common.config.RuoYiConfig;
import com.xzzn.common.core.controller.BaseController; import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult; import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo; import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.common.utils.file.FileUploadUtils; import com.xzzn.common.utils.file.FileUploadUtils;
import com.xzzn.common.utils.file.MimeTypeUtils; import com.xzzn.common.utils.file.MimeTypeUtils;
import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.common.utils.poi.ExcelUtil;
import com.xzzn.ems.domain.EmsSiteSetting; import com.xzzn.ems.domain.EmsSiteSetting;
import com.xzzn.ems.domain.vo.DeviceUpdateRequest; import com.xzzn.ems.domain.vo.DeviceUpdateRequest;
import com.xzzn.ems.domain.vo.DevicesSettingVo;
import com.xzzn.ems.domain.vo.PointDataRequest; import com.xzzn.ems.domain.vo.PointDataRequest;
import com.xzzn.ems.domain.vo.PointQueryResponse; import com.xzzn.ems.domain.vo.PointQueryResponse;
import com.xzzn.ems.domain.vo.SiteDeviceListVo; import com.xzzn.ems.domain.vo.SiteDeviceListVo;
import com.xzzn.ems.mapper.EmsPointMatchMapper; import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest;
import com.xzzn.ems.domain.vo.SingleBatteryConfigInitRequest;
import com.xzzn.ems.domain.vo.SingleBatteryConfigInitResultVo;
import com.xzzn.ems.domain.vo.SingleBatteryMonitorImportResultVo;
import com.xzzn.ems.domain.vo.SingleBatteryMonitorImportRowVo;
import com.xzzn.ems.domain.vo.WorkStatusEnumMappingSaveRequest;
import com.xzzn.ems.service.IEmsDeviceSettingService; import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.IEmsSiteService; import com.xzzn.ems.service.IEmsSiteService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.validation.Valid;
/** /**
* * 绔欑偣閰嶇疆
* 站点配置
*
*/ */
@RestController @RestController
@RequestMapping("/ems/siteConfig") @RequestMapping("/ems/siteConfig")
public class EmsSiteConfigController extends BaseController{ public class EmsSiteConfigController extends BaseController {
@Autowired @Autowired
private IEmsSiteService iEmsSiteService; private IEmsSiteService iEmsSiteService;
@ -39,155 +53,169 @@ public class EmsSiteConfigController extends BaseController{
@Autowired @Autowired
private IEmsDeviceSettingService iEmsDeviceSettingService; private IEmsDeviceSettingService iEmsDeviceSettingService;
/**
* 获取站点列表
*/
@GetMapping("/getSiteInfoList") @GetMapping("/getSiteInfoList")
public TableDataInfo getSiteInfoList(@RequestParam String siteName, @RequestParam String startTime, @RequestParam String endTime) public TableDataInfo getSiteInfoList(@RequestParam String siteName, @RequestParam String startTime, @RequestParam String endTime) {
{
startPage(); startPage();
List<EmsSiteSetting> list = iEmsSiteService.getAllSiteInfoList(siteName,startTime,endTime); List<EmsSiteSetting> list = iEmsSiteService.getAllSiteInfoList(siteName, startTime, endTime);
return getDataTable(list); return getDataTable(list);
} }
/** @PostMapping("/addSite")
* 获取设备列表-分页 public AjaxResult addSite(@RequestBody EmsSiteSetting emsSiteSetting) {
*/ emsSiteSetting.setCreateBy(getUsername());
return toAjax(iEmsSiteService.addSite(emsSiteSetting));
}
@PostMapping("/updateSite")
public AjaxResult updateSite(@RequestBody EmsSiteSetting emsSiteSetting) {
emsSiteSetting.setUpdateBy(getUsername());
return toAjax(iEmsSiteService.updateSite(emsSiteSetting));
}
@GetMapping("/getDeviceInfoList") @GetMapping("/getDeviceInfoList")
public TableDataInfo getDeviceInfoList(@RequestParam String siteId, public TableDataInfo getDeviceInfoList(@RequestParam(value = "siteId", required = false) String siteId,
@RequestParam(value = "deviceCategory", required = false) String deviceCategory) @RequestParam(value = "deviceCategory", required = false) String deviceCategory) {
{
startPage(); startPage();
List<SiteDeviceListVo> list = iEmsSiteService.getAllDeviceList(siteId, deviceCategory); List<SiteDeviceListVo> list = iEmsSiteService.getAllDeviceListNoDisp(siteId, deviceCategory);
return getDataTable(list); return getDataTable(list);
} }
/**
* 获取设备详细信息
*/
@GetMapping("/getDeviceDetailInfo") @GetMapping("/getDeviceDetailInfo")
public AjaxResult getDeviceDetailInfo(@RequestParam Long id) public AjaxResult getDeviceDetailInfo(@RequestParam Long id) {
{
return success(iEmsDeviceSettingService.getDeviceDetailInfo(id)); return success(iEmsDeviceSettingService.getDeviceDetailInfo(id));
} }
/**
* 获取设备列表-不分页
*/
@GetMapping("/getDeviceList") @GetMapping("/getDeviceList")
public AjaxResult getDeviceInfoList2(@RequestParam String siteId) public AjaxResult getDeviceInfoList2(@RequestParam String siteId) {
{
return success(iEmsSiteService.getAllDeviceList(siteId, null)); return success(iEmsSiteService.getAllDeviceList(siteId, null));
} }
/**
* 获取所有设备类别
*/
@GetMapping("/getDeviceCategory") @GetMapping("/getDeviceCategory")
public AjaxResult getDeviceCategory() public AjaxResult getDeviceCategory() {
{
return success(iEmsDeviceSettingService.getDeviceCategory()); return success(iEmsDeviceSettingService.getDeviceCategory());
} }
/**
* 新增设备
*/
@PostMapping("/addDevice") @PostMapping("/addDevice")
public AjaxResult addDevice(@RequestBody EmsDevicesSetting devicesSetting) public AjaxResult addDevice(@RequestBody DevicesSettingVo devicesSetting) {
{
int result = iEmsDeviceSettingService.addDevice(devicesSetting); int result = iEmsDeviceSettingService.addDevice(devicesSetting);
if (result > 0) { if (result > 0) {
return AjaxResult.success(result); return AjaxResult.success(result);
} else {
return AjaxResult.error("该设备已存在");
} }
return AjaxResult.error("璇ヨ澶囧凡瀛樺湪");
} }
/**
* 上传设备图片
*/
@PostMapping("/uploadDeviceImg") @PostMapping("/uploadDeviceImg")
public AjaxResult uploadDeviceImg(@RequestParam("avatarfile") MultipartFile file) throws Exception public AjaxResult uploadDeviceImg(@RequestParam("avatarfile") MultipartFile file) throws Exception {
{
if (!file.isEmpty()) { if (!file.isEmpty()) {
String avatar = FileUploadUtils.upload(RuoYiConfig.getDevicePath(), file, MimeTypeUtils.IMAGE_EXTENSION); String avatar = FileUploadUtils.upload(RuoYiConfig.getDevicePath(), file, MimeTypeUtils.IMAGE_EXTENSION);
AjaxResult ajax = AjaxResult.success(); AjaxResult ajax = AjaxResult.success();
ajax.put("imgUrl", avatar); ajax.put("imgUrl", avatar);
return ajax; return ajax;
} }
return error("上传图片异常,请联系管理员"); return error("涓婁紶鍥剧墖寮傚父锛岃鑱旂郴绠$悊鍛?");
} }
/**
* 修改Modbus设备配置
*/
@PostMapping("/updateDevice") @PostMapping("/updateDevice")
public AjaxResult updateDevice(@RequestBody EmsDevicesSetting emsDevicesSetting) public AjaxResult updateDevice(@RequestBody DevicesSettingVo emsDevicesSetting) {
{
int result = iEmsDeviceSettingService.updateDevice(emsDevicesSetting); int result = iEmsDeviceSettingService.updateDevice(emsDevicesSetting);
if (result > 0) { if (result > 0) {
return AjaxResult.success(result); return AjaxResult.success(result);
} else if (result == -1) { } else if (result == -1) {
return AjaxResult.error("数据不存在"); return AjaxResult.error("鏁版嵁涓嶅瓨鍦?");
} else if (result == -2) { } else if (result == -2) {
return AjaxResult.error("该设备已存在"); return AjaxResult.error("璇ヨ澶囧凡瀛樺湪");
} }
return AjaxResult.success(result); return AjaxResult.success(result);
} }
/**
* 删除Modbus设备配置
*/
@DeleteMapping("/deleteService/{id}") @DeleteMapping("/deleteService/{id}")
public AjaxResult deleteService(@PathVariable Long id) public AjaxResult deleteService(@PathVariable Long id) {
{
return toAjax(iEmsDeviceSettingService.deleteEmsDevicesSettingById(id)); return toAjax(iEmsDeviceSettingService.deleteEmsDevicesSettingById(id));
} }
/**
* 单个站点单个设备点位查询-点位清单
*/
@GetMapping("/getDevicePointList") @GetMapping("/getDevicePointList")
public TableDataInfo getDevicePointList(@Validated PointDataRequest request) public TableDataInfo getDevicePointList(@Validated PointDataRequest request) {
{
List<PointQueryResponse> result = iEmsDeviceSettingService.getSingleSiteDevicePoints(request); List<PointQueryResponse> result = iEmsDeviceSettingService.getSingleSiteDevicePoints(request);
return getDataTable2(result); return getDataTable2(result);
} }
/**
* 获取指定站点下的所有设备类别
*/
@GetMapping("/getSiteAllDeviceCategory") @GetMapping("/getSiteAllDeviceCategory")
public AjaxResult getSiteAllDeviceCategory(String siteId) public AjaxResult getSiteAllDeviceCategory(String siteId) {
{
return success(iEmsDeviceSettingService.getSiteAllDeviceCategory(siteId)); return success(iEmsDeviceSettingService.getSiteAllDeviceCategory(siteId));
} }
/**
* 根据设备类别获取父类的设备id
*/
@GetMapping("/getParentDeviceId") @GetMapping("/getParentDeviceId")
public AjaxResult getParentDeviceId(@RequestParam String siteId, @RequestParam String deviceCategory) public AjaxResult getParentDeviceId(@RequestParam String siteId, @RequestParam String deviceCategory) {
{
return success(iEmsSiteService.getParentCategoryDeviceId(siteId, deviceCategory)); return success(iEmsSiteService.getParentCategoryDeviceId(siteId, deviceCategory));
} }
/**
* 获取指定站点下的指定设备类型的设备
*/
@GetMapping("/getDeviceListBySiteAndCategory") @GetMapping("/getDeviceListBySiteAndCategory")
public AjaxResult getDeviceListBySiteAndCategory(String siteId,String deviceCategory) public AjaxResult getDeviceListBySiteAndCategory(String siteId, String deviceCategory) {
{
return success(iEmsDeviceSettingService.getDeviceListBySiteAndCategory(siteId, deviceCategory)); return success(iEmsDeviceSettingService.getDeviceListBySiteAndCategory(siteId, deviceCategory));
} }
/** @GetMapping("/getSingleMonitorProjectPointMapping")
* PCS设备开关机 public AjaxResult getSingleMonitorProjectPointMapping(@RequestParam String siteId) {
*/ return success(iEmsDeviceSettingService.getSiteMonitorProjectPointMapping(siteId));
}
@PostMapping("/saveSingleMonitorProjectPointMapping")
public AjaxResult saveSingleMonitorProjectPointMapping(@RequestBody SiteMonitorProjectPointMappingSaveRequest request) {
int rows = iEmsDeviceSettingService.saveSiteMonitorProjectPointMapping(request, getUsername());
return AjaxResult.success(rows);
}
@GetMapping("/getSingleMonitorWorkStatusEnumMappings")
public AjaxResult getSingleMonitorWorkStatusEnumMappings(@RequestParam String siteId) {
return success(iEmsDeviceSettingService.getSiteWorkStatusEnumMappings(siteId));
}
@PostMapping("/saveSingleMonitorWorkStatusEnumMappings")
public AjaxResult saveSingleMonitorWorkStatusEnumMappings(@RequestBody WorkStatusEnumMappingSaveRequest request) {
int rows = iEmsDeviceSettingService.saveSiteWorkStatusEnumMappings(request.getSiteId(), request.getMappings(), getUsername());
return AjaxResult.success(rows);
}
@GetMapping("/downloadSingleBatteryMonitorImportTemplate")
public void downloadSingleBatteryMonitorImportTemplate(HttpServletResponse response) {
ExcelUtil<SingleBatteryMonitorImportRowVo> util = new ExcelUtil<>(SingleBatteryMonitorImportRowVo.class);
util.importTemplateExcel(response, "单体电池导入模板", "单体电池导入模板");
}
@PostMapping("/importSingleBatteryMonitorMappings")
public AjaxResult importSingleBatteryMonitorMappings(@RequestParam("siteId") String siteId,
@RequestParam("file") MultipartFile file) throws Exception {
ExcelUtil<SingleBatteryMonitorImportRowVo> util = new ExcelUtil<>(SingleBatteryMonitorImportRowVo.class);
List<SingleBatteryMonitorImportRowVo> rowList = util.importExcel(file.getInputStream(), 1);
if (rowList == null) {
return AjaxResult.error("导入失败:未读取到任何数据,请检查 Excel 文件内容");
}
List<SingleBatteryMonitorImportRowVo> validRowList = new ArrayList<>();
for (int i = 0; i < rowList.size(); i++) {
SingleBatteryMonitorImportRowVo row = rowList.get(i);
if (row == null) {
continue;
}
row.setRowNum(i + 3);
validRowList.add(row);
}
if (validRowList.isEmpty()) {
return AjaxResult.error("导入失败:未识别到有效数据,请使用系统导出的最新模板,并保持表头名称不变");
}
SingleBatteryMonitorImportResultVo result = iEmsDeviceSettingService.importSingleBatteryMonitorMappings(siteId, validRowList, getUsername());
return AjaxResult.success(result);
}
@PostMapping("/initializeSingleBatteryMonitorMappings")
public AjaxResult initializeSingleBatteryMonitorMappings(@RequestBody SingleBatteryConfigInitRequest request) {
SingleBatteryConfigInitResultVo result = iEmsDeviceSettingService.initializeSingleBatteryMonitorMappings(request, getUsername());
return AjaxResult.success(result);
}
// @PreAuthorize("@ss.hasPermi('system:device:onAndOff')")
@Log(title = "寮€鍏虫満", businessType = BusinessType.UPDATE)
@PostMapping("/updateDeviceStatus") @PostMapping("/updateDeviceStatus")
public AjaxResult updateDeviceStatus(@Valid @RequestBody DeviceUpdateRequest request) public AjaxResult updateDeviceStatus(@Valid @RequestBody DeviceUpdateRequest request) {
{
return success(iEmsDeviceSettingService.updateDeviceStatus(request)); return success(iEmsDeviceSettingService.updateDeviceStatus(request));
} }
} }

View File

@ -9,9 +9,14 @@ import com.xzzn.ems.domain.vo.BatteryDataStatsListVo;
import com.xzzn.ems.domain.vo.DateSearchRequest; import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.domain.vo.RunningGraphRequest; import com.xzzn.ems.domain.vo.RunningGraphRequest;
import com.xzzn.ems.domain.vo.SiteBatteryDataList; import com.xzzn.ems.domain.vo.SiteBatteryDataList;
import com.xzzn.ems.domain.vo.SiteMonitorDataSaveRequest;
import com.xzzn.ems.domain.vo.SiteMonitorRuningInfoVo;
import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.IEmsSiteService; import com.xzzn.ems.service.IEmsSiteService;
import com.xzzn.ems.service.IEmsStatsReportService; import com.xzzn.ems.service.IEmsStatsReportService;
import com.xzzn.ems.service.ISingleSiteService; import com.xzzn.ems.service.ISingleSiteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -26,6 +31,8 @@ import java.util.List;
@RestController @RestController
@RequestMapping("/ems/siteMonitor") @RequestMapping("/ems/siteMonitor")
public class EmsSiteMonitorController extends BaseController{ public class EmsSiteMonitorController extends BaseController{
private static final Logger log = LoggerFactory.getLogger(EmsSiteMonitorController.class);
private static final String RUNNING_GRAPH_CTRL_DEBUG = "RunningGraphCtrlDebug";
@Autowired @Autowired
private ISingleSiteService iSingleSiteService; private ISingleSiteService iSingleSiteService;
@ -33,6 +40,8 @@ public class EmsSiteMonitorController extends BaseController{
private IEmsSiteService iEmsSiteService; private IEmsSiteService iEmsSiteService;
@Autowired @Autowired
private IEmsStatsReportService iemsStatsReportService; private IEmsStatsReportService iemsStatsReportService;
@Autowired
private IEmsDeviceSettingService iEmsDeviceSettingService;
/** /**
* 获取单站首页数据 * 获取单站首页数据
@ -43,6 +52,15 @@ public class EmsSiteMonitorController extends BaseController{
return success(iSingleSiteService.getSiteMonitorDataVo(siteId)); return success(iSingleSiteService.getSiteMonitorDataVo(siteId));
} }
/**
* 获取单站首页总累计运行数据(基于日表)
*/
@GetMapping("/homeTotalView")
public AjaxResult getSingleSiteHomeTotalView(@RequestParam String siteId)
{
return success(iSingleSiteService.getSiteMonitorTotalDataVo(siteId));
}
/** /**
* 单站监控-设备监控-实时运行头部数据 * 单站监控-设备监控-实时运行头部数据
*/ */
@ -56,27 +74,75 @@ public class EmsSiteMonitorController extends BaseController{
* 单站监控-设备监控-实时运行曲线图数据 * 单站监控-设备监控-实时运行曲线图数据
*/ */
@GetMapping("/runningGraph/storagePower") @GetMapping("/runningGraph/storagePower")
public AjaxResult getRunningGraphStorage(RunningGraphRequest request) public AjaxResult getRunningGraphStorage(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{ {
return success(iSingleSiteService.getRunningGraphStorage(request)); SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphStorage(request);
int deviceCount = data == null || data.getPcsPowerList() == null ? 0 : data.getPcsPowerList().size();
log.info("{} storage, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, deviceCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
deviceCount);
return success(data);
} }
@GetMapping("/runningGraph/pcsMaxTemp") @GetMapping("/runningGraph/pcsMaxTemp")
public AjaxResult getRunningGraphPcsMaxTemp(RunningGraphRequest request) public AjaxResult getRunningGraphPcsMaxTemp(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{ {
return success(iSingleSiteService.getRunningGraphPcsMaxTemp(request)); SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphPcsMaxTemp(request);
int deviceCount = data == null || data.getPcsMaxTempList() == null ? 0 : data.getPcsMaxTempList().size();
log.info("{} pcsMaxTemp, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, deviceCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
deviceCount);
return success(data);
} }
@GetMapping("/runningGraph/batteryAveSoc") @GetMapping("/runningGraph/batteryAveSoc")
public AjaxResult getRunningGraphBatterySoc(RunningGraphRequest request) public AjaxResult getRunningGraphBatterySoc(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{ {
return success(iSingleSiteService.getRunningGraphBatterySoc(request)); SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphBatterySoc(request);
int pointCount = data == null || data.getBatteryAveSOCList() == null ? 0 : data.getBatteryAveSOCList().size();
log.info("{} batteryAveSoc, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, pointCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
pointCount);
return success(data);
} }
@GetMapping("/runningGraph/batteryAveTemp") @GetMapping("/runningGraph/batteryAveTemp")
public AjaxResult getRunningGraphBatteryTemp(RunningGraphRequest request) public AjaxResult getRunningGraphBatteryTemp(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{ {
return success(iSingleSiteService.getRunningGraphBatteryTemp(request)); SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphBatteryTemp(request);
int pointCount = data == null || data.getBatteryAveTempList() == null ? 0 : data.getBatteryAveTempList().size();
log.info("{} batteryAveTemp, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, pointCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
pointCount);
return success(data);
} }
/** /**
@ -151,9 +217,7 @@ public class EmsSiteMonitorController extends BaseController{
{ {
startPage(); startPage();
SiteBatteryDataList siteBatteryDataList = new SiteBatteryDataList(); SiteBatteryDataList siteBatteryDataList = new SiteBatteryDataList();
// 簇最大最小单体id数据
List<BMSBatteryDataList> clusterBatteryDataList = iSingleSiteService.getClusterBatteryList(siteId,stackDeviceId,clusterDeviceId);
siteBatteryDataList.setClusterList(clusterBatteryDataList);
// 单体电池数据 // 单体电池数据
List<BatteryDataStatsListVo> List = iSingleSiteService.getClusterDataInfoList(clusterDeviceId,siteId,stackDeviceId,batteryId); List<BatteryDataStatsListVo> List = iSingleSiteService.getClusterDataInfoList(clusterDeviceId,siteId,stackDeviceId,batteryId);
// 对batteryList进行分页处理 // 对batteryList进行分页处理
@ -228,4 +292,31 @@ public class EmsSiteMonitorController extends BaseController{
return error("缺少必传项"); return error("缺少必传项");
} }
} }
/**
* 单站监控项目点位配置查询
*/
@GetMapping("/getProjectPointMapping")
public AjaxResult getProjectPointMapping(@RequestParam String siteId)
{
return success(iEmsDeviceSettingService.getSiteMonitorProjectPointMapping(siteId));
}
/**
* 单站监控项目展示数据查询(配置字段 + 字段值)
*/
@GetMapping("/getProjectDisplayData")
public AjaxResult getProjectDisplayData(@RequestParam String siteId)
{
return success(iEmsDeviceSettingService.getSiteMonitorProjectDisplay(siteId));
}
/**
* 单站监控项目展示数据写入
*/
@PostMapping("/saveProjectDisplayData")
public AjaxResult saveProjectDisplayData(@RequestBody SiteMonitorDataSaveRequest request)
{
return AjaxResult.success(iEmsDeviceSettingService.saveSiteMonitorProjectData(request, getUsername()));
}
} }

View File

@ -1,19 +1,33 @@
package com.xzzn.web.controller.ems; package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController; import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult; import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo; import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.common.utils.StringUtils; import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.vo.*; import com.xzzn.ems.domain.vo.AmmeterRevenueStatisListVo;
import com.xzzn.ems.domain.vo.AmmeterStatisListVo;
import com.xzzn.ems.domain.vo.ClusterStatisListVo;
import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.domain.vo.StatisAmmeterDateRequest;
import com.xzzn.ems.domain.vo.StatisClusterDateRequest;
import com.xzzn.ems.domain.vo.WeatherSyncResultVo;
import com.xzzn.ems.service.IEmsStatsReportService; import com.xzzn.ems.service.IEmsStatsReportService;
import org.springframework.beans.factory.annotation.Autowired; import com.xzzn.ems.service.IEmsWeatherSyncService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/** /**
* 单站监控-统计报表 * 单站监控-统计报表
* *
@ -26,6 +40,8 @@ public class EmsStatisticalReportController extends BaseController
@Autowired @Autowired
private IEmsStatsReportService ieEmsStatsReportService; private IEmsStatsReportService ieEmsStatsReportService;
@Autowired
private IEmsWeatherSyncService iEmsWeatherSyncService;
/** /**
* 概率统计-收益指标查询 * 概率统计-收益指标查询
@ -118,6 +134,39 @@ public class EmsStatisticalReportController extends BaseController
return getDataTable(dataList); return getDataTable(dataList);
} }
/**
* 统计报表-电表报表(直接基于 ems_daily_energy_data
*/
@GetMapping("/getAmmeterDataFromDaily")
public TableDataInfo getAmmeterDataFromDaily(StatisAmmeterDateRequest requestVo)
{
startPage();
List<AmmeterStatisListVo> dataList = ieEmsStatsReportService.getAmmeterDataResult(requestVo);
return getDataTable(dataList);
}
/**
* 导出电表报表
*/
@PreAuthorize("@ss.hasPermi('system:ammeterData:export')")
@Log(title = "电表报表", businessType = BusinessType.EXPORT)
@PostMapping("/exportAmmeterData")
public void exportAmmeterData(HttpServletResponse response, StatisAmmeterDateRequest requestVo)
{
ieEmsStatsReportService.exportAmmeterData(response, requestVo);
}
/**
* 导出电表报表(直接基于 ems_daily_energy_data
*/
@PreAuthorize("@ss.hasPermi('system:ammeterData:export')")
@Log(title = "电表报表", businessType = BusinessType.EXPORT)
@PostMapping("/exportAmmeterDataFromDaily")
public void exportAmmeterDataFromDaily(HttpServletResponse response, StatisAmmeterDateRequest requestVo)
{
ieEmsStatsReportService.exportAmmeterData(response, requestVo);
}
/** /**
* 概率统计-电表收益报表 * 概率统计-电表收益报表
*/ */
@ -129,6 +178,17 @@ public class EmsStatisticalReportController extends BaseController
return getDataTable(dataList); return getDataTable(dataList);
} }
/**
* 导出收益报表
*/
@PreAuthorize("@ss.hasPermi('system:ammeterRevenueData:export')")
@Log(title = "收益报表", businessType = BusinessType.EXPORT)
@PostMapping("/exportAmmeterRevenueData")
public void exportAmmeterRevenueData(HttpServletResponse response, StatisAmmeterDateRequest requestVo)
{
ieEmsStatsReportService.exportAmmeterRevenueData(response, requestVo);
}
/** /**
* 概率统计-功率曲线 * 概率统计-功率曲线
*/ */
@ -142,4 +202,19 @@ public class EmsStatisticalReportController extends BaseController
} }
} }
/**
* 手动触发天气同步
*/
@PostMapping("/syncWeatherByDateRange")
public AjaxResult syncWeatherByDateRange(StatisAmmeterDateRequest requestVo)
{
if (StringUtils.isEmpty(requestVo.getSiteId())
|| StringUtils.isEmpty(requestVo.getStartTime())
|| StringUtils.isEmpty(requestVo.getEndTime())) {
return error("缺少必传项: siteId/startTime/endTime");
}
WeatherSyncResultVo resultVo = iEmsWeatherSyncService.syncWeatherByDateRange(requestVo);
return success(resultVo);
}
} }

View File

@ -0,0 +1,48 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
import com.xzzn.ems.service.IEmsStrategyRuntimeConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 策略运行参数配置Controller
*/
@RestController
@RequestMapping("/system/strategyRuntimeConfig")
public class EmsStrategyRuntimeConfigController extends BaseController {
@Autowired
private IEmsStrategyRuntimeConfigService runtimeConfigService;
/**
* 按站点ID获取策略运行参数
*/
@GetMapping("/getBySiteId")
public AjaxResult getBySiteId(String siteId) {
if (StringUtils.isEmpty(siteId)) {
return error("缺少必填字段siteId");
}
return success(runtimeConfigService.getBySiteId(siteId));
}
/**
* 保存策略运行参数按siteId新增/更新)
*/
@PostMapping("/save")
public AjaxResult save(@RequestBody EmsStrategyRuntimeConfig config) {
if (config == null || StringUtils.isEmpty(config.getSiteId())) {
return error("缺少必填字段siteId");
}
config.setCreateBy(getUsername());
config.setUpdateBy(getUsername());
return toAjax(runtimeConfigService.saveBySiteId(config));
}
}

View File

@ -6,22 +6,26 @@ import com.xzzn.common.enums.TopicHandleType;
import com.xzzn.common.utils.StringUtils; import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsMqttTopicConfig; import com.xzzn.ems.domain.EmsMqttTopicConfig;
import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper; import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.service.*; import com.xzzn.ems.service.IDeviceDataProcessService;
import com.xzzn.ems.service.IMqttSyncLogService;
import com.xzzn.framework.manager.MqttLifecycleManager; import com.xzzn.framework.manager.MqttLifecycleManager;
import com.xzzn.framework.web.service.MqttPublisher; import com.xzzn.framework.web.service.MqttPublisher;
import com.xzzn.framework.web.service.MqttSubscriber; import com.xzzn.framework.web.service.MqttSubscriber;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener; import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.List;
@Service @Service
public class MqttMessageController implements MqttPublisher, MqttSubscriber { public class MqttMessageController implements MqttPublisher, MqttSubscriber {
@ -30,29 +34,19 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber {
private final MqttLifecycleManager mqttLifecycleManager; private final MqttLifecycleManager mqttLifecycleManager;
@Autowired
private IEmsMqttMessageService emsMqttMessageService;
@Autowired
private IFXXDataProcessService fXXDataProcessService;
@Autowired
private IDDSDataProcessService dDSDataProcessService;
@Autowired @Autowired
private IDeviceDataProcessService deviceDataProcessService; private IDeviceDataProcessService deviceDataProcessService;
@Autowired
private IFXXAlarmDataProcessService fXXAlarmDataProcessService;
@Autowired @Autowired
private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper; private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper;
@Autowired @Autowired
private IEmsStrategyService emsStrategyService;
@Autowired
private IMqttSyncLogService iMqttSyncLogService; private IMqttSyncLogService iMqttSyncLogService;
@Autowired @Autowired
private RedisCache redisCache; private RedisCache redisCache;
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Autowired @Autowired
public MqttMessageController(MqttLifecycleManager mqttLifecycleManager) { public MqttMessageController(MqttLifecycleManager mqttLifecycleManager) {
@ -128,50 +122,32 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber {
private void handleSystemStatus(String topic, MqttMessage message) { private void handleSystemStatus(String topic, MqttMessage message) {
String payload = new String(message.getPayload()); String payload = new String(message.getPayload());
System.out.println("[SYSTEM] Status update: " + payload); System.out.println("[SYSTEM] Status update: " + payload);
try {
emsMqttMessageService.insertMqttOriginalMessage(topic,payload);
} catch (Exception e) {
log.error("Failed to process system status message: " + e.getMessage(), e);
}
} }
// 处理设备数据 // 处理设备数据
private void handleDeviceData(String topic, MqttMessage message) { private void handleDeviceData(String topic, MqttMessage message) {
String payload = new String(message.getPayload()); String payload = new String(message.getPayload());
log.info("[DEVICE] data: " + payload); threadPoolTaskExecutor.execute(() -> {
try { log.debug("[DEVICE] data: {}", payload);
// 业务处理逻辑 try {
// if (topic.startsWith("021_DDS")) { deviceDataProcessService.handleDeviceData(payload, getSiteIdByTopic(topic));
// dDSDataProcessService.handleDdsData(payload); } catch (Exception e) {
// } else if (topic.startsWith("021_FXX")) { log.error("Failed to process device data message: {}", e.getMessage(), e);
// fXXDataProcessService.handleFxData(payload); }
// } });
deviceDataProcessService.handleDeviceData(payload, getSiteIdByTopic(topic));
emsMqttMessageService.insertMqttOriginalMessage(topic, payload);
} catch (Exception e) {
log.error("Failed to process device data message: " + e.getMessage(), e);
}
} }
// 处理告警数据 // 处理告警数据
private void handleAlarmData(String topic, MqttMessage message) { private void handleAlarmData(String topic, MqttMessage message) {
String payload = new String(message.getPayload()); String payload = new String(message.getPayload());
System.out.println("[DEVICE] data: " + payload); threadPoolTaskExecutor.execute(() -> {
try { try {
// 业务处理逻辑 deviceDataProcessService.handleAlarmData(payload, getSiteIdByTopic(topic));
// if (topic.startsWith("021_FXX")) { } catch (Exception e) {
// fXXAlarmDataProcessService.handleFxAlarmData(payload); log.error("Failed to process device alarm data message: {}", e.getMessage(), e);
// } }
deviceDataProcessService.handleAlarmData(payload, getSiteIdByTopic(topic)); });
emsMqttMessageService.insertMqttOriginalMessage(topic, payload);
} catch (Exception e) {
log.error("Failed to process device alarm data message: " + e.getMessage(), e);
}
} }
@ -182,78 +158,67 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber {
siteId = topicConfig.getSiteId(); siteId = topicConfig.getSiteId();
redisCache.setCacheObject(RedisKeyConstants.SITE_ID + topic, siteId); redisCache.setCacheObject(RedisKeyConstants.SITE_ID + topic, siteId);
} }
log.info("当前处理数据站点:" + siteId + ",topic: " + topic);
return siteId; return siteId;
} }
// 处理运行策略数据:云端-本地 // 处理运行策略数据:云端-本地
private void handleStrategyData(String topic, MqttMessage message) { private void handleStrategyData(String topic, MqttMessage message) {
String payload = new String(message.getPayload()); String payload = new String(message.getPayload());
System.out.println("[处理运行策略数据] data: " + payload); threadPoolTaskExecutor.execute(() -> {
try { try {
// 业务处理逻辑 iMqttSyncLogService.handleMqttStrategyData(payload);
iMqttSyncLogService.handleMqttStrategyData(payload); } catch (Exception e) {
log.error("Failed to process strategy data message: {}", e.getMessage(), e);
emsMqttMessageService.insertMqttOriginalMessage(topic,payload); }
} catch (Exception e) { });
log.error("Failed to process strategy data message: " + e.getMessage(), e);
}
} }
// 处理设备保护告警策略数据:云端-本地 // 处理设备保护告警策略数据:云端-本地
private void handleFaultProtPlanData(String topic, MqttMessage message) { private void handleFaultProtPlanData(String topic, MqttMessage message) {
String payload = new String(message.getPayload()); String payload = new String(message.getPayload());
System.out.println("[处理设备保护告警策略数据] data: " + payload); threadPoolTaskExecutor.execute(() -> {
try { try {
// 业务处理逻辑 iMqttSyncLogService.handleMqttPlanData(payload);
iMqttSyncLogService.handleMqttPlanData(payload); } catch (Exception e) {
log.error("Failed to process fault plan data message: {}", e.getMessage(), e);
emsMqttMessageService.insertMqttOriginalMessage(topic,payload); }
} catch (Exception e) { });
log.error("Failed to process fault plan data message: " + e.getMessage(), e);
}
} }
// 处理保护策略告警信息:本地-云端 // 处理保护策略告警信息:本地-云端
private void handleFaultAlarmData(String topic, MqttMessage message) { private void handleFaultAlarmData(String topic, MqttMessage message) {
String payload = new String(message.getPayload()); String payload = new String(message.getPayload());
System.out.println("[处理本地保护策略告警信息到云端] data: " + payload); threadPoolTaskExecutor.execute(() -> {
try { try {
// 业务处理逻辑 iMqttSyncLogService.handleFaultAlarmData(payload);
iMqttSyncLogService.handleFaultAlarmData(payload); } catch (Exception e) {
log.error("Failed to process fault plan alarm data message: {}", e.getMessage(), e);
emsMqttMessageService.insertMqttOriginalMessage(topic,payload); }
} catch (Exception e) { });
log.error("Failed to process fault plan alarm data message: " + e.getMessage(), e);
}
} }
// 处理保护策略下发日志:本地-云端 // 处理保护策略下发日志:本地-云端
private void handleFaultPlanIssueData(String topic, MqttMessage message) { private void handleFaultPlanIssueData(String topic, MqttMessage message) {
String payload = new String(message.getPayload()); String payload = new String(message.getPayload());
System.out.println("[处理本地保护策略下发日志到云端] data: " + payload); threadPoolTaskExecutor.execute(() -> {
try { try {
// 业务处理逻辑 iMqttSyncLogService.handleFaultPlanIssueData(payload);
iMqttSyncLogService.handleFaultPlanIssueData(payload); } catch (Exception e) {
log.error("Failed to process fault plan issue log message: {}", e.getMessage(), e);
emsMqttMessageService.insertMqttOriginalMessage(topic,payload); }
} catch (Exception e) { });
log.error("Failed to process fault plan issue log message: " + e.getMessage(), e);
}
} }
// 处理设备状态变更日志:本地-云端 // 处理设备状态变更日志:本地-云端
private void handleDeviceChangeLogData(String topic, MqttMessage message) { private void handleDeviceChangeLogData(String topic, MqttMessage message) {
String payload = new String(message.getPayload()); String payload = new String(message.getPayload());
System.out.println("[处理本地的保护策略告警信息到云端] data: " + payload); threadPoolTaskExecutor.execute(() -> {
try { try {
// 业务处理逻辑 iMqttSyncLogService.handleDeviceChangeLogData(payload);
iMqttSyncLogService.handleDeviceChangeLogData(payload); } catch (Exception e) {
log.error("Failed to process device change log message: {}", e.getMessage(), e);
emsMqttMessageService.insertMqttOriginalMessage(topic,payload); }
} catch (Exception e) { });
log.error("Failed to process device change log message: " + e.getMessage(), e);
}
} }
@Override @Override
@ -283,4 +248,4 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber {
} }
} }
} }

View File

@ -0,0 +1,32 @@
package com.xzzn.web.controller.system;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.ems.domain.vo.BizRemarkBatchGetRequest;
import com.xzzn.ems.domain.vo.BizRemarkSaveRequest;
import com.xzzn.ems.service.ISysBizRemarkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/system/bizRemark")
public class SysBizRemarkController extends BaseController
{
@Autowired
private ISysBizRemarkService sysBizRemarkService;
@PostMapping("/batchGet")
public AjaxResult batchGet(@RequestBody BizRemarkBatchGetRequest request)
{
return success(sysBizRemarkService.getRemarkMap(request.getBizType(), request.getBizKey1(), request.getBizKey2List()));
}
@PostMapping("/save")
public AjaxResult save(@RequestBody BizRemarkSaveRequest request)
{
return toAjax(sysBizRemarkService.saveRemark(request, getUsername()));
}
}

View File

@ -205,3 +205,10 @@ modbus:
poll: poll:
interval: "0 */5 * * * *" # 5分钟间隔 interval: "0 */5 * * * *" # 5分钟间隔
timeout: 30000 # 30秒超时 timeout: 30000 # 30秒超时
weather:
api:
enabled: true
base-url: https://archive-api.open-meteo.com/v1/archive
api-key:
timezone: Asia/Shanghai

View File

@ -205,3 +205,24 @@ modbus:
poll: poll:
interval: "0 */5 * * * *" # 5分钟间隔 interval: "0 */5 * * * *" # 5分钟间隔
timeout: 30000 # 30秒超时 timeout: 30000 # 30秒超时
weather:
api:
enabled: true
base-url: https://archive-api.open-meteo.com/v1/archive
api-key:
timezone: Asia/Shanghai
influxdb:
enabled: true
url: http://172.17.0.7:8086/
api-token: B_1HvHbUhubQQhLdI0XtNVw7maWS1aIjVZQ1a3PGD6b-VNg3_JUo_jHgZmjeBKYXnGATNdIqfpl-FAVbJ4UIPg==
write-method: POST
read-method: GET
write-path: /api/v2/write
query-path: /query
org: ems
bucket: point_data
database: ems_point_data
retention-policy: autogen
measurement: mqtt_point_data

View File

@ -199,6 +199,20 @@ mqtt:
topic: topic:
siteId: siteId:
influxdb:
enabled: true
url: http://122.51.194.184:8086/
api-token: F2XcmBzZsWcz90ikU2_t7UXY2fzWuf2ruVp1BkusNkIS_gwrQZuiaIjl33XQMQajm7vSI6TQSRnpPSx5CXThlA==
write-method: POST
read-method: GET
write-path: /api/v2/write
query-path: /query
org: ems
bucket: point_data
database: ems_point_data
retention-policy: autogen
measurement: mqtt_point_data
modbus: modbus:
pool: pool:
max-total: 20 max-total: 20
@ -207,3 +221,10 @@ modbus:
poll: poll:
interval: "0 */5 * * * *" # 5分钟间隔 interval: "0 */5 * * * *" # 5分钟间隔
timeout: 30000 # 30秒超时 timeout: 30000 # 30秒超时
weather:
api:
enabled: true
base-url: https://archive-api.open-meteo.com/v1/archive
api-key:
timezone: Asia/Shanghai

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<configuration> <configuration>
<!-- 日志存放路径 --> <!-- 日志存放路径 -->
<property name="log.path" value="/etc/xzzn/logs" /> <property name="log.path" value="logs" />
<!-- 日志输出格式 --> <!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" /> <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
@ -90,4 +90,4 @@
<logger name="sys-user" level="info"> <logger name="sys-user" level="info">
<appender-ref ref="sys-user"/> <appender-ref ref="sys-user"/>
</logger> </logger>
</configuration> </configuration>

View File

@ -119,7 +119,7 @@
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
</dependency> </dependency>
<!-- modbus --> <!-- modbus4j (保留兼容) -->
<dependency> <dependency>
<groupId>com.infiniteautomation</groupId> <groupId>com.infiniteautomation</groupId>
<artifactId>modbus4j</artifactId> <artifactId>modbus4j</artifactId>
@ -127,4 +127,4 @@
</dependencies> </dependencies>
</project> </project>

View File

@ -96,9 +96,16 @@ public class RedisKeyConstants
public static final String TOPIC_FAILED_ALRAM_RECORD = "topic_failed_"; public static final String TOPIC_FAILED_ALRAM_RECORD = "topic_failed_";
/** topic 内没有数据设备维度告警 */ /** topic 内没有数据设备维度告警 */
public static final String TOPIC_EMPTY_ALARM_RECORD = "topic_empty_"; public static final String TOPIC_EMPTY_ALARM_RECORD = "topic_empty_";
/** modbus读取 没有数据设备维度告警 */
public static final String MODBUS_EMPTY_ALARM_RECORD = "modbus_empty_";
/** modbus读取 设备离线告警 */
public static final String MODBUS_OFFLINE_ALARM_RECORD = "modbus_offline_";
/** 设备信息初始化 */ /** 设备信息初始化 */
public static final String INIT_DEVICE_INFO = "init_device_info"; public static final String INIT_DEVICE_INFO = "init_device_info";
/** 设备配置缓存(按站点+设备) */
public static final String DEVICE_SETTING = "DEVICE_SETTING_";
/** 告警匹配信息 */ /** 告警匹配信息 */
public static final String ALARM_MATCH_INFO = "alarm_message_info"; public static final String ALARM_MATCH_INFO = "alarm_message_info";
@ -118,4 +125,19 @@ public class RedisKeyConstants
/** 每个设备最新数据-设置失效时间-判断是否正常同步数据 */ /** 每个设备最新数据-设置失效时间-判断是否正常同步数据 */
public static final String SYNC_DATA_ALARM = "SYNC_DATA_ALARM_"; public static final String SYNC_DATA_ALARM = "SYNC_DATA_ALARM_";
/** 点位配置缓存(按站点+设备) */
public static final String POINT_CONFIG_DEVICE = "POINT_CONFIG_DEVICE_";
/** 点位配置缓存(按站点+pointId */
public static final String POINT_CONFIG_POINT = "POINT_CONFIG_POINT_";
/** 单站监控最新数据(按站点+模块) */
public static final String SITE_MONITOR_LATEST = "SITE_MONITOR_LATEST_";
/** 单站监控点位映射(按站点) */
public static final String SITE_MONITOR_POINT_MATCH = "SITE_MONITOR_POINT_MATCH_";
/** 站点保护约束(按站点) */
public static final String PROTECTION_CONSTRAINT = "PROTECTION_CONSTRAINT_";
} }

View File

@ -92,6 +92,8 @@ public class SysUser extends BaseEntity
/** 角色ID */ /** 角色ID */
private Long roleId; private Long roleId;
private String belongSite;
public SysUser() public SysUser()
{ {
@ -310,6 +312,14 @@ public class SysUser extends BaseEntity
this.roleId = roleId; this.roleId = roleId;
} }
public String getBelongSite() {
return belongSite;
}
public void setBelongSite(String belongSite) {
this.belongSite = belongSite;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -332,7 +342,7 @@ public class SysUser extends BaseEntity
.append("updateBy", getUpdateBy()) .append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime()) .append("updateTime", getUpdateTime())
.append("remark", getRemark()) .append("remark", getRemark())
.append("dept", getDept()) .append("belongSite", getBelongSite())
.toString(); .toString();
} }
} }

View File

@ -1,61 +1,111 @@
package com.xzzn.common.core.modbus; package com.xzzn.common.core.modbus;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster; import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.ip.IpParameters;
import com.xzzn.common.core.modbus.domain.DeviceConfig; import com.xzzn.common.core.modbus.domain.DeviceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PreDestroy; /**
* Modbus连接管理器
import org.apache.commons.pool2.impl.GenericObjectPool; * 使用长连接模式,维护连接缓存,复用已有连接
import org.apache.commons.pool2.impl.GenericObjectPoolConfig; */
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component @Component
public class Modbus4jConnectionManager { public class Modbus4jConnectionManager {
private static final Logger logger = LoggerFactory.getLogger(Modbus4jConnectionManager.class); private static final Logger logger = LoggerFactory.getLogger(Modbus4jConnectionManager.class);
private final Map<String, GenericObjectPool<ModbusMaster>> connectionPools = new ConcurrentHashMap<>(); private final ModbusFactory modbusFactory = new ModbusFactory();
private final Map<String, ModbusMaster> connectionCache = new ConcurrentHashMap<>();
/**
* 获取或创建连接(长连接模式)
*/
public ModbusMaster borrowMaster(DeviceConfig config) throws Exception { public ModbusMaster borrowMaster(DeviceConfig config) throws Exception {
String poolKey = getPoolKey(config); String key = buildConnectionKey(config);
GenericObjectPool<ModbusMaster> pool = connectionPools.computeIfAbsent(poolKey, key -> { ModbusMaster master = connectionCache.get(key);
PooledModbusMasterFactory factory = new PooledModbusMasterFactory(config.getHost(), config.getPort());
GenericObjectPoolConfig<ModbusMaster> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(5); // 池中最大连接数
poolConfig.setMinIdle(1); // 最小空闲连接数
poolConfig.setTestOnBorrow(true); // 借用时测试连接有效性
return new GenericObjectPool<>(factory, poolConfig);
});
return pool.borrowObject();
}
public void returnMaster(DeviceConfig config, ModbusMaster master) {
if (master == null) { if (master == null) {
return; synchronized (this) {
master = connectionCache.get(key);
if (master == null) {
master = createNewConnection(config);
connectionCache.put(key, master);
logger.info("创建新Modbus长连接: {}:{}", config.getHost(), config.getPort());
}
}
} }
String poolKey = getPoolKey(config);
GenericObjectPool<ModbusMaster> pool = connectionPools.get(poolKey);
if (pool != null) { return master;
pool.returnObject(master); }
/**
* 归还连接(长连接模式,不关闭)
*/
public void returnMaster(DeviceConfig config, ModbusMaster master) {
}
/**
* 废弃连接(发生异常时关闭并移除)
*/
public void invalidateMaster(DeviceConfig config, ModbusMaster master) {
String key = buildConnectionKey(config);
connectionCache.remove(key);
destroyMaster(master, config);
logger.warn("废弃并移除Modbus连接: {}:{}", config.getHost(), config.getPort());
}
private ModbusMaster createNewConnection(DeviceConfig config) throws Exception {
IpParameters params = new IpParameters();
params.setHost(config.getHost());
params.setPort(config.getPort());
params.setEncapsulated(false);
ModbusMaster master = modbusFactory.createTcpMaster(params, true);
master.init();
return master;
}
private void destroyMaster(ModbusMaster master, DeviceConfig config) {
if (master != null) {
try {
master.destroy();
logger.debug("已关闭Modbus连接: {}:{}", config.getHost(), config.getPort());
} catch (Exception e) {
logger.warn("关闭Modbus连接异常: {}:{}", config.getHost(), config.getPort(), e);
}
} }
} }
private String getPoolKey(DeviceConfig config) { private String buildConnectionKey(DeviceConfig config) {
return config.getHost() + ":" + config.getPort(); return config.getHost() + ":" + config.getPort();
} }
@PreDestroy /**
public void shutdown() { * 关闭所有连接
connectionPools.values().forEach(GenericObjectPool::close); */
public void closeAllConnections() {
for (Map.Entry<String, ModbusMaster> entry : connectionCache.entrySet()) {
try {
if (entry.getValue() != null) {
entry.getValue().destroy();
}
} catch (Exception e) {
logger.warn("关闭Modbus连接异常: {}", entry.getKey(), e);
}
}
connectionCache.clear();
logger.info("已关闭所有Modbus连接");
} }
} }

View File

@ -1,5 +1,6 @@
package com.xzzn.common.core.modbus; package com.xzzn.common.core.modbus;
import com.alibaba.fastjson2.JSON;
import com.serotonin.modbus4j.BatchRead; import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults; import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusMaster; import com.serotonin.modbus4j.ModbusMaster;
@ -22,15 +23,18 @@ import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.ModBusType; import com.xzzn.common.enums.ModBusType;
import com.xzzn.common.enums.RegisterType; import com.xzzn.common.enums.RegisterType;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import static com.xzzn.common.enums.RegisterType.COIL; import static com.xzzn.common.enums.RegisterType.COIL;
@ -40,6 +44,14 @@ import static com.xzzn.common.enums.RegisterType.DISCRETE_INPUT;
public class ModbusProcessor { public class ModbusProcessor {
private static final Logger logger = LoggerFactory.getLogger(ModbusProcessor.class); private static final Logger logger = LoggerFactory.getLogger(ModbusProcessor.class);
@Value("${modbus.read-timeout:8000}")
private int readTimeout;
@Value("${modbus.write-timeout:5000}")
private int writeTimeout;
@Value("${modbus.read-retries:1}")
private int readRetries;
@Value("${modbus.write-retries:1}")
private int writeRetries;
@Autowired @Autowired
private RedisCache redisCache; private RedisCache redisCache;
@ -47,19 +59,53 @@ public class ModbusProcessor {
private Modbus4jConnectionManager connectionManager; private Modbus4jConnectionManager connectionManager;
public boolean writeDataToDevice(DeviceConfig config) { public boolean writeDataToDevice(DeviceConfig config) {
logger.info("writeDataToDevice: {}", JSON.toJSONString(config));
ModbusMaster master = null; ModbusMaster master = null;
boolean result = true; boolean result = false;
boolean hasError = false;
try { try {
master = connectionManager.borrowMaster(config); master = connectionManager.borrowMaster(config);
// 设置了Modbus通信的超时时间为3000毫秒3秒。当主设备与从设备通信时若在3秒内未收到响应则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。 master.setTimeout(writeTimeout);
master.setTimeout(5000); master.setRetries(0);
writeTagValue(master, config, config.getWriteTags()); result = writeTagValue(master, config, config.getWriteTags());
} catch (Exception e) {
logger.error("Failed to borrow connection or write to devices '{}'", config.getDeviceName(), e);
hasError = true;
}
finally {
if (master != null) {
if (hasError) {
// 发生异常时废弃连接,下次重新创建
connectionManager.invalidateMaster(config, master);
} else {
// 正常时归还连接
connectionManager.returnMaster(config, master);
}
}
}
return result;
}
public boolean writeDataToDeviceWithRetry(DeviceConfig config) {
logger.info("writeDataToDevice: {}", JSON.toJSONString(config));
ModbusMaster master = null;
boolean result;
try {
master = connectionManager.borrowMaster(config);
master.setTimeout(writeTimeout); // 设置超时时间
master.setRetries(0);
// 使用重试装饰器
ModbusMaster finalMaster = master;
result = RetryableModbusOperation.executeWithRetry(() -> {
return writeTagValue(finalMaster, config, config.getWriteTags());
}, writeRetries); // 最大重试次数由配置控制
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to borrow connection or write to devices '{}'", config.getDeviceName(), e); logger.error("Failed to borrow connection or write to devices '{}'", config.getDeviceName(), e);
result = false; result = false;
} } finally {
finally {
// 关键:无论成功与否,都必须将连接归还到池中
if (master != null) { if (master != null) {
connectionManager.returnMaster(config, master); connectionManager.returnMaster(config, master);
} }
@ -67,7 +113,7 @@ public class ModbusProcessor {
return result; return result;
} }
public void writeTagValue(ModbusMaster master, DeviceConfig config, List<WriteTagConfig> tags) { public boolean writeTagValue(ModbusMaster master, DeviceConfig config, List<WriteTagConfig> tags) {
tags.forEach(tag -> { tags.forEach(tag -> {
Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE; Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1)); int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1));
@ -76,11 +122,19 @@ public class ModbusProcessor {
logger.info("Register type: {}, address: {}, firstDigit: {}", registerType, tag.getAddress(), firstDigit); logger.info("Register type: {}, address: {}, firstDigit: {}", registerType, tag.getAddress(), firstDigit);
switch (registerType) { switch (registerType) {
case COIL: { case COIL: {
writeCoilRequest(master, config.getSlaveId(), address, Boolean.parseBoolean(String.valueOf(tag.getValue()))); boolean result = writeCoilRequest(master, config.getSlaveId(), address, Boolean.parseBoolean(String.valueOf(tag.getValue())));
tag.setWrite(result);
break; break;
} }
case HOLDING_REGISTER: { case HOLDING_REGISTER: {
writeRegisterRequest(master, config.getSlaveId(), address, Integer.parseInt(String.valueOf(tag.getValue()))); Number value = Double.parseDouble(String.valueOf(tag.getValue()));
double doubleValue = value.doubleValue();
// 检查是否在16位有符号整数范围内
if (doubleValue < -32768 || doubleValue > 32767) {
logger.warn("Value {} out of range for 16-bit signed register at address {}", doubleValue, address);
}
boolean result = writeRegisterRequest(master, config.getSlaveId(), address, (int) doubleValue);
tag.setWrite(result);
break; break;
} }
default: default:
@ -88,9 +142,15 @@ public class ModbusProcessor {
break; break;
} }
}); });
List<WriteTagConfig> collect = tags.stream().filter(tag -> !Objects.equals(tag.isWrite(), true)).collect(Collectors.toList());
if (!collect.isEmpty()) {
return false;
}
return true;
} }
public static void writeCoilRequest(ModbusMaster master, int slaveId, int address, boolean value) { public static boolean writeCoilRequest(ModbusMaster master, int slaveId, int address, boolean value) {
try { try {
WriteCoilRequest request = new WriteCoilRequest(slaveId, address, value); WriteCoilRequest request = new WriteCoilRequest(slaveId, address, value);
WriteCoilResponse response = (WriteCoilResponse)master.send(request); WriteCoilResponse response = (WriteCoilResponse)master.send(request);
@ -98,10 +158,12 @@ public class ModbusProcessor {
logger.info("Write coil failed: " + response.getExceptionMessage()); logger.info("Write coil failed: " + response.getExceptionMessage());
} else { } else {
logger.info("Write coil successful"); logger.info("Write coil successful");
return true;
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to write coil value '{}' to address '{}'", value, address, e); logger.error("Failed to write coil value '{}' to address '{}'", value, address, e);
} }
return false;
} }
public static void writeCoilsRequest(ModbusMaster master, int slaveId, int address, boolean[] values) { public static void writeCoilsRequest(ModbusMaster master, int slaveId, int address, boolean[] values) {
@ -118,7 +180,7 @@ public class ModbusProcessor {
} }
} }
public static void writeRegisterRequest(ModbusMaster master, int slaveId, int address, int value) { public static boolean writeRegisterRequest(ModbusMaster master, int slaveId, int address, int value) {
try { try {
WriteRegisterRequest request = new WriteRegisterRequest(slaveId, address, value); WriteRegisterRequest request = new WriteRegisterRequest(slaveId, address, value);
WriteRegisterResponse response = (WriteRegisterResponse)master.send(request); WriteRegisterResponse response = (WriteRegisterResponse)master.send(request);
@ -126,10 +188,12 @@ public class ModbusProcessor {
logger.info("Write register failed: " + response.getExceptionMessage()); logger.info("Write register failed: " + response.getExceptionMessage());
} else { } else {
logger.info("Write register successful"); logger.info("Write register successful");
return true;
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to write register value '{}' to address '{}'", value, address, e); logger.error("Failed to write register value '{}' to address '{}'", value, address, e);
} }
return false;
} }
public static void writeRegistersRequest(ModbusMaster master, int slaveId, int address, short[] values) { public static void writeRegistersRequest(ModbusMaster master, int slaveId, int address, short[] values) {
@ -160,7 +224,7 @@ public class ModbusProcessor {
public static void setRegisterValue(ModbusMaster master, int slaveId, int address, int value) { public static void setRegisterValue(ModbusMaster master, int slaveId, int address, int value) {
// 写入单个保持寄存器(从站, 地址, 16位整数) // 写入单个保持寄存器(从站, 地址, 16位整数)
BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, address, DataType.TWO_BYTE_INT_UNSIGNED); BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, address, DataType.TWO_BYTE_INT_SIGNED);
try { try {
// 写入整数值到保持寄存器 // 写入整数值到保持寄存器
master.setValue(holdingLocator, value); master.setValue(holdingLocator, value);
@ -170,14 +234,18 @@ public class ModbusProcessor {
} }
} }
public ModbusMaster borrowMaster(DeviceConfig config) throws Exception {
ModbusMaster master = connectionManager.borrowMaster(config);
// 设置了Modbus通信的超时时间为5000毫秒5秒。当主设备与从设备通信时若在5秒内未收到响应则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。
master.setTimeout(readTimeout);
master.setRetries(readRetries);
return master;
}
public Map<String, Object> readDataFromDevice(DeviceConfig config, ModbusMaster master) { public Map<String, Object> readDataFromDevice(DeviceConfig config, ModbusMaster master) {
Map<String, Object> deviceData = new HashMap<>(); Map<String, Object> deviceData = new HashMap<>();
// ModbusMaster master = null; // 将master的声明提前 boolean hasError = false;
try { try {
master = connectionManager.borrowMaster(config);
// 设置了Modbus通信的超时时间为3000毫秒3秒。当主设备与从设备通信时若在3秒内未收到响应则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。
master.setTimeout(5000);
BatchResults<String> results = readTagValues(master, config.getSlaveId(), config.getTags()); BatchResults<String> results = readTagValues(master, config.getSlaveId(), config.getTags());
for (TagConfig tag : config.getTags()) { for (TagConfig tag : config.getTags()) {
if (Objects.equals(tag.getDataType(), "FOUR_BYTE_FLOAT_DBCA")){ if (Objects.equals(tag.getDataType(), "FOUR_BYTE_FLOAT_DBCA")){
@ -187,28 +255,22 @@ public class ModbusProcessor {
}else { }else {
deviceData.put(tag.getKey(), results.getValue(tag.getKey())); deviceData.put(tag.getKey(), results.getValue(tag.getKey()));
} }
// try {
// Object value = readTagValue(master, config.getSlaveId(), tag);
// if (value != null) {
// deviceData.put(tag.getKey(), value);
// }
// } catch (Exception e) {
// logger.error("Failed to read tag '{}' from devices '{}'", tag.getKey(), config.getDeviceName(), e);
// }
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to borrow connection or read from devices '{}'", config.getDeviceName(), e); logger.error("Failed read from devices '{}'", config.getDeviceName(), e);
hasError = true;
} }
finally { finally {
// 关键:无论成功与否,都必须将连接归还到池中
if (master != null) { if (master != null) {
connectionManager.returnMaster(config, master); if (hasError) {
// 发生异常时废弃连接,下次重新创建
connectionManager.invalidateMaster(config, master);
} else {
// 正常时归还连接
connectionManager.returnMaster(config, master);
}
} }
} }
String deviceNumber = config.getDeviceNumber();
redisCache.setCacheObject(deviceNumber, deviceData);
return deviceData; return deviceData;
} }
@ -284,69 +346,101 @@ public class ModbusProcessor {
private BatchResults<String> readTagValues(ModbusMaster master, int slaveId, List<TagConfig> tags) throws Exception { private BatchResults<String> readTagValues(ModbusMaster master, int slaveId, List<TagConfig> tags) throws Exception {
try { try {
BatchRead<String> batch = new BatchRead<>(); BatchResults<String> results = sendBatchRead(master, slaveId, tags);
tags.forEach(tag -> { logBatchResults(tags, results);
Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
Map<String, Integer> DATA_LENGTH = ModBusType.LENGTH;
int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1));
int address = 0;
int addressLength = tag.getAddress().length();
int exp = (int) Math.pow(10, addressLength-1);
if (firstDigit != 0){
int digit = Integer.parseInt(tag.getAddress());
address = digit % (exp);
}else {
address = Integer.parseInt(tag.getAddress());
}
RegisterType registerType = type.get(firstDigit);
int dataLength = DATA_LENGTH.get(tag.getDataType());
switch (registerType) {
case COIL: {
BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, address);
batch.addLocator(tag.getKey(), loc);
break;
}
case DISCRETE_INPUT: {
BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, address);
batch.addLocator(tag.getKey(), loc);
break;
}
case HOLDING_REGISTER: {
// logger.info("HOLDING_REGISTER: {}",tag.getAddress());
if (dataLength == 28){
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, 4);
batch.addLocator(tag.getKey(), locator);
}else {
BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, address, dataLength);
batch.addLocator(tag.getKey(), loc);
}
break;
}
case INPUT_REGISTER: {
// logger.info("INPUT_REGISTER: {}",tag.getAddress());
BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, address, dataLength);
batch.addLocator(tag.getKey(), loc);
break;
}
}
});
BatchResults<String> results = master.send(batch);
for (TagConfig tag : tags){
if (tag.getBit()!=null){
logger.info("批处理读取寄存器成功: {}",tag.getAddress() +"(" + tag.getBit() + "):" + results.getValue(tag.getKey()));
}else {
logger.info("批处理读取寄存器成功: {}",tag.getAddress() + ":" + results.getValue(tag.getKey()));
}
}
return results; return results;
}catch (Exception e){ } catch (Exception e){
if (isTimeoutException(e)) {
logger.warn("批量读取超时尝试降级为单点读取slaveId: {}", slaveId);
BatchResults<String> fallback = new BatchResults<>();
for (TagConfig tag : tags) {
Object value = readTagValue(master, slaveId, tag);
fallback.addResult(tag.getKey(), value);
}
logBatchResults(tags, fallback);
return fallback;
}
logger.error("Failed to read master '{}'", slaveId, e); logger.error("Failed to read master '{}'", slaveId, e);
throw new Exception(e); throw new Exception(e);
} }
} }
private BatchResults<String> sendBatchRead(ModbusMaster master, int slaveId, List<TagConfig> tags) throws Exception {
BatchRead<String> batch = new BatchRead<>();
tags.forEach(tag -> {
Map<Integer, RegisterType> type = ModBusType.REGISTER_TYPE;
Map<String, Integer> DATA_LENGTH = ModBusType.LENGTH;
int firstDigit = Integer.parseInt(tag.getAddress().substring(0, 1));
int address = 0;
int addressLength = tag.getAddress().length();
int exp = (int) Math.pow(10, addressLength - 1);
if (firstDigit != 0) {
int digit = Integer.parseInt(tag.getAddress());
address = digit % (exp);
} else {
address = Integer.parseInt(tag.getAddress());
}
RegisterType registerType = type.get(firstDigit);
int dataLength = DATA_LENGTH.get(tag.getDataType());
switch (registerType) {
case COIL: {
BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, address);
batch.addLocator(tag.getKey(), loc);
break;
}
case DISCRETE_INPUT: {
BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, address);
batch.addLocator(tag.getKey(), loc);
break;
}
case HOLDING_REGISTER: {
if (dataLength == 28) {
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, address, 4);
batch.addLocator(tag.getKey(), locator);
} else {
BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, address, dataLength);
batch.addLocator(tag.getKey(), loc);
}
break;
}
case INPUT_REGISTER: {
BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, address, dataLength);
batch.addLocator(tag.getKey(), loc);
break;
}
}
});
return master.send(batch);
}
private void logBatchResults(List<TagConfig> tags, BatchResults<String> results) {
List<String> logInfoList = new ArrayList<>();
for (TagConfig tag : tags) {
StringBuilder logInfo = new StringBuilder();
logInfo.append(tag.getAddress());
if (tag.getBit() != null) {
logInfo.append("(").append(tag.getBit()).append("):");
} else {
logInfo.append(":");
}
logInfo.append(results.getValue(tag.getKey()));
logInfoList.add(logInfo.toString());
}
logger.info("批处理读取寄存器成功: {}", JSON.toJSONString(logInfoList));
}
private boolean isTimeoutException(Throwable e) {
Throwable current = e;
while (current != null) {
if (current instanceof com.serotonin.modbus4j.sero.messaging.TimeoutException) {
return true;
}
current = current.getCause();
}
return false;
}
public static float convertValueToFloat(Object value) { public static float convertValueToFloat(Object value) {
if (!(value instanceof Number)) { if (!(value instanceof Number)) {
throw new IllegalArgumentException("Input must be a Number"); throw new IllegalArgumentException("Input must be a Number");

View File

@ -0,0 +1,37 @@
package com.xzzn.common.core.modbus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Modbus重试
*
*/
public class RetryableModbusOperation {
private static final Logger logger = LoggerFactory.getLogger(RetryableModbusOperation.class);
public interface ModbusOperation<T> {
T execute() throws Exception;
}
public static <T> T executeWithRetry(ModbusOperation<T> operation, int maxRetries) {
int retryCount = 0;
Exception lastException = null;
while (retryCount <= maxRetries) {
try {
return operation.execute();
} catch (Exception e) {
lastException = e;
retryCount++;
if (retryCount <= maxRetries) {
logger.info("Operation failed, retrying... (Attempt {})", retryCount);
}
}
}
logger.error("Max retries ({}) reached. Last error: {}", maxRetries,
lastException != null ? lastException.getMessage() : "Unknown");
throw new RuntimeException("Operation failed after " + maxRetries + " retries", lastException);
}
}

View File

@ -0,0 +1,106 @@
package com.xzzn.common.core.modbus.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "modbus")
public class ModbusProperties {
private PoolConfig pool = new PoolConfig();
private TimeoutConfig timeout = new TimeoutConfig();
public PoolConfig getPool() {
return pool;
}
public void setPool(PoolConfig pool) {
this.pool = pool;
}
public TimeoutConfig getTimeout() {
return timeout;
}
public void setTimeout(TimeoutConfig timeout) {
this.timeout = timeout;
}
public static class PoolConfig {
private int maxTotal = 20;
private int maxIdle = 10;
private int minIdle = 3;
private long maxWait = 5000;
private long timeBetweenEvictionRuns = 30000;
private long minEvictableIdleTime = 60000;
public int getMaxTotal() {
return maxTotal;
}
public void setMaxTotal(int maxTotal) {
this.maxTotal = maxTotal;
}
public int getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMinIdle() {
return minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public long getMaxWait() {
return maxWait;
}
public void setMaxWait(long maxWait) {
this.maxWait = maxWait;
}
public long getTimeBetweenEvictionRuns() {
return timeBetweenEvictionRuns;
}
public void setTimeBetweenEvictionRuns(long timeBetweenEvictionRuns) {
this.timeBetweenEvictionRuns = timeBetweenEvictionRuns;
}
public long getMinEvictableIdleTime() {
return minEvictableIdleTime;
}
public void setMinEvictableIdleTime(long minEvictableIdleTime) {
this.minEvictableIdleTime = minEvictableIdleTime;
}
}
public static class TimeoutConfig {
private int read = 10000;
private int write = 5000;
public int getRead() {
return read;
}
public void setRead(int read) {
this.read = read;
}
public int getWrite() {
return write;
}
public void setWrite(int write) {
this.write = write;
}
}
}

View File

@ -3,6 +3,7 @@ package com.xzzn.common.core.modbus.domain;
public class WriteTagConfig { public class WriteTagConfig {
private Object value; private Object value;
private String address; private String address;
private boolean isWrite;
public Object getValue() { public Object getValue() {
return value; return value;
@ -19,4 +20,12 @@ public class WriteTagConfig {
public void setAddress(String address) { public void setAddress(String address) {
this.address = address; this.address = address;
} }
public boolean isWrite() {
return isWrite;
}
public void setWrite(boolean write) {
isWrite = write;
}
} }

View File

@ -1,8 +1,5 @@
package com.xzzn.common.enums; package com.xzzn.common.enums;
import java.util.Arrays;
import java.util.List;
/** /**
* device-通信状态 * device-通信状态
* *
@ -11,10 +8,7 @@ import java.util.List;
public enum DeviceRunningStatus public enum DeviceRunningStatus
{ {
OFFLINE("0", "离线"), OFFLINE("0", "离线"),
STANDBY("1", "待机") , ONLINE("1", "在线");
RUNNING("2", "运行"),
FAULT("3", "故障"),
SHUTDOWN("4", "停机");
private final String code; private final String code;
private final String info; private final String info;
@ -35,15 +29,4 @@ public enum DeviceRunningStatus
return info; return info;
} }
/** 停机状态 */
public static List<String> getShutdownCodeList()
{
return Arrays.asList(SHUTDOWN.getCode());
}
/** 运行状态 */
public static List<String> getRunningCodeList()
{
return Arrays.asList(STANDBY.getCode(), RUNNING.getCode(), FAULT.getCode());
}
} }

View File

@ -35,8 +35,6 @@ public class ModBusType {
LENGTH.put("FOUR_BYTE_FLOAT",DataType.FOUR_BYTE_FLOAT); LENGTH.put("FOUR_BYTE_FLOAT",DataType.FOUR_BYTE_FLOAT);
LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED",DataType.FOUR_BYTE_FLOAT_SWAPPED); LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED",DataType.FOUR_BYTE_FLOAT_SWAPPED);
LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED_INVERTED",DataType.FOUR_BYTE_FLOAT_SWAPPED_INVERTED); LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED_INVERTED",DataType.FOUR_BYTE_FLOAT_SWAPPED_INVERTED);
LENGTH.put("FOUR_BYTE_FLOAT_INVERTED",DataType.FOUR_BYTE_FLOAT_INVERTED);
LENGTH.put("FOUR_BYTE_FLOAT_SWAPPED_SWAPPED",DataType.FOUR_BYTE_FLOAT_SWAPPED_SWAPPED);
LENGTH.put("EIGHT_BYTE_INT_UNSIGNED",DataType.EIGHT_BYTE_INT_UNSIGNED); LENGTH.put("EIGHT_BYTE_INT_UNSIGNED",DataType.EIGHT_BYTE_INT_UNSIGNED);
LENGTH.put("EIGHT_BYTE_INT_SIGNED",DataType.EIGHT_BYTE_INT_SIGNED); LENGTH.put("EIGHT_BYTE_INT_SIGNED",DataType.EIGHT_BYTE_INT_SIGNED);
LENGTH.put("EIGHT_BYTE_INT_UNSIGNED_SWAPPED",DataType.EIGHT_BYTE_INT_UNSIGNED_SWAPPED); LENGTH.put("EIGHT_BYTE_INT_UNSIGNED_SWAPPED",DataType.EIGHT_BYTE_INT_UNSIGNED_SWAPPED);

View File

@ -1,45 +0,0 @@
package com.xzzn.common.enums;
public enum PcsControlCommand {
TOTAL_START(0x0004, "总运行"),
TOTAL_STOP(0x0010, "总停机"),
TOTAL_RESET(0x0080, "总复位"),
// CLUSTER1_START(0x1004, "电池簇1运行"),
// CLUSTER1_STOP(0x1010, "电池簇1停机"),
// CLUSTER1_RESET(0x1080, "电池簇1复位"),
// CLUSTER2_START(0x2004, "电池簇2运行"),
// CLUSTER2_STOP(0x2010, "电池簇2停机"),
// CLUSTER2_RESET(0x2080, "电池簇2复位"),
// CLUSTER3_START(0x3004, "电池簇3运行"),
// CLUSTER3_STOP(0x3010, "电池簇3停机"),
// CLUSTER3_RESET(0x3080, "电池簇3复位"),
// CLUSTER4_START(0x4004, "电池簇4运行"),
// CLUSTER4_STOP(0x4010, "电池簇4停机"),
// CLUSTER4_RESET(0x4080, "电池簇4复位"),
;
private final int code;
private final String description;
PcsControlCommand(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
public static PcsControlCommand fromDeviceStatus(String deviceStatus) {
if (DeviceRunningStatus.RUNNING.getCode().equals(deviceStatus)) {
return PcsControlCommand.TOTAL_START;
} else if (DeviceRunningStatus.SHUTDOWN.getCode().equals(deviceStatus) ) {
return PcsControlCommand.TOTAL_STOP;
}
return null;
}
}

View File

@ -0,0 +1,32 @@
package com.xzzn.common.enums;
/**
* SOC限制 (%) 1 = 开0 = 关
*
* @author xzzn
*/
public enum SocLimit
{
OFF(0, ""),
ON(1, ""),
;
private final Integer code;
private final String info;
SocLimit(Integer code, String info)
{
this.code = code;
this.info = info;
}
public Integer getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}

View File

@ -7,7 +7,7 @@ package com.xzzn.common.enums;
*/ */
public enum WorkStatus public enum WorkStatus
{ {
NORMAL("0", "正常"), ABNORMAL("1", "异常"), STOP("2", "停止"); NORMAL("0", "运行"), STOP("1", "停机"), ABNORMAL("2", "故障");
private final String code; private final String code;
private final String info; private final String info;

View File

@ -0,0 +1,33 @@
package com.xzzn.common.enums;
public enum YesOrNo
{
YES(1, "", "Y"),
NO(0, "", "N");
private final Integer code;
private final String info;
private final String value;
YesOrNo(Integer code, String info, String value)
{
this.code = code;
this.info = info;
this.value = value;
}
public Integer getCode()
{
return code;
}
public String getInfo()
{
return info;
}
public String getValue()
{
return value;
}
}

View File

@ -1,7 +1,5 @@
package com.xzzn.common.utils; package com.xzzn.common.utils;
import com.xzzn.common.annotation.Log;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -161,6 +159,14 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
} }
/**
* 计算相差分钟
*/
public static int differentMinutesByMillisecond(Date date1, Date date2)
{
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 60)));
}
/** /**
* 计算时间差 * 计算时间差
* *
@ -410,7 +416,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
// 减去一天得到昨天 // 减去一天得到昨天
LocalDate yesterday = today.minusDays(1); LocalDate yesterday = today.minusDays(1);
// 定义日期格式化器 // 定义日期格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYYMMDD); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYY_MM_DD);
// 格式化并返回 // 格式化并返回
return yesterday.format(formatter); return yesterday.format(formatter);
} }

View File

@ -1,5 +1,8 @@
package com.xzzn.common.utils; package com.xzzn.common.utils;
import com.xzzn.common.constant.Constants;
import com.xzzn.common.core.text.StrFormatter;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -7,9 +10,8 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import com.xzzn.common.constant.Constants;
import com.xzzn.common.core.text.StrFormatter;
/** /**
* 字符串工具类 * 字符串工具类
@ -743,6 +745,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
} }
public static String getString(Object s){ public static String getString(Object s){
if (s == null) return null;
String result; String result;
try { try {
result = String.valueOf(s); result = String.valueOf(s);

View File

@ -50,11 +50,7 @@
<groupId>org.eclipse.paho</groupId> <groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
</dependency> </dependency>
<!-- 轮询 -->
<dependency>
<groupId>net.wimpi</groupId>
<artifactId>j2mod</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.retry</groupId> <groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId> <artifactId>spring-retry</artifactId>

View File

@ -10,18 +10,23 @@ import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper; import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper; import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher; import com.xzzn.framework.web.service.MqttPublisher;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory; import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap; import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.*;
/** /**
* 策略运行切面同步 * 策略运行切面同步
* 云端 - 本地 * 云端 - 本地
@ -30,7 +35,7 @@ import java.util.*;
@Component @Component
public class StrategyRunningSyncAspect { public class StrategyRunningSyncAspect {
private static final Log logger = LogFactory.getLog(StrategyRunningSyncAspect.class); private static final Logger logger = LoggerFactory.getLogger(StrategyRunningSyncAspect.class);
@Autowired @Autowired
private MqttPublisher mqttPublisher; private MqttPublisher mqttPublisher;

View File

@ -4,23 +4,34 @@ import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.utils.StringUtils; import com.xzzn.common.utils.StringUtils;
import com.xzzn.common.utils.bean.BeanUtils; import com.xzzn.common.utils.bean.BeanUtils;
import com.xzzn.ems.domain.*; import com.xzzn.ems.domain.EmsStrategy;
import com.xzzn.ems.domain.EmsStrategyRunning;
import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.MqttSyncLog;
import com.xzzn.ems.domain.vo.SyncStrategyTempVo; import com.xzzn.ems.domain.vo.SyncStrategyTempVo;
import com.xzzn.ems.mapper.*; import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.EmsStrategyMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher; import com.xzzn.framework.web.service.MqttPublisher;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory; import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap; import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.*;
/** /**
* 策略模板数据同步 * 策略模板数据同步
* 云端 - 本地 * 云端 - 本地
@ -29,7 +40,7 @@ import java.util.*;
@Component @Component
public class StrategyTempSyncAspect { public class StrategyTempSyncAspect {
private static final Log logger = LogFactory.getLog(StrategyTempSyncAspect.class); private static final Logger logger = LoggerFactory.getLogger(StrategyTempSyncAspect.class);
@Autowired @Autowired
private MqttPublisher mqttPublisher; private MqttPublisher mqttPublisher;
@ -47,7 +58,7 @@ public class StrategyTempSyncAspect {
// 用ThreadLocal暂存删除前的对象 // 用ThreadLocal暂存删除前的对象
private ThreadLocal<EmsStrategyTemp> beforeDeleteThreadLocal = new ThreadLocal<>(); private ThreadLocal<EmsStrategyTemp> beforeDeleteThreadLocal = new ThreadLocal<>();
@Before("execution(* com.xzzn.ems.mapper.EmsStrategyTempMapper.deleteEmsStrategyTempById(..)) && args(templateId)") @Before("execution(* com.xzzn.ems.mapper.EmsStrategyTempMapper.deleteTempByTempId(..)) && args(templateId)")
public void beforeDelete(JoinPoint joinPoint, String templateId) { public void beforeDelete(JoinPoint joinPoint, String templateId) {
// 查询删除前的数据-仅存一获取siteId // 查询删除前的数据-仅存一获取siteId
List<EmsStrategyTemp> tempList = emsStrategyTempMapper.selectStrategyTempByTempId(templateId); List<EmsStrategyTemp> tempList = emsStrategyTempMapper.selectStrategyTempByTempId(templateId);
@ -56,7 +67,7 @@ public class StrategyTempSyncAspect {
} }
} }
@Pointcut("(execution(* com.xzzn.ems.mapper.EmsStrategyTempMapper.deleteEmsStrategyTempById(..)) && args(templateId)) ") @Pointcut("(execution(* com.xzzn.ems.mapper.EmsStrategyTempMapper.deleteTempByTempId(..)) && args(templateId)) ")
public void deletePointCut(String templateId) { public void deletePointCut(String templateId) {
logger.info("【删除策略模版切面】StrategyTempSyncAspect 被实例化"); logger.info("【删除策略模版切面】StrategyTempSyncAspect 被实例化");
} }
@ -94,7 +105,7 @@ public class StrategyTempSyncAspect {
} }
// 数据转换 // 数据转换
SyncStrategyTempVo tempVo = convertEntity(insertEntity); SyncStrategyTempVo tempVo = convertEntity(insertEntity);
String content = objectMapper.writeValueAsString(tempVo); String content = JSON.toJSONString(tempVo);
message.setContent(content); message.setContent(content);
// 发布到MQTT主题 // 发布到MQTT主题

View File

@ -3,22 +3,32 @@ package com.xzzn.framework.aspectj;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.xzzn.common.utils.StringUtils; import com.xzzn.common.utils.StringUtils;
import com.xzzn.common.utils.bean.BeanUtils; import com.xzzn.common.utils.bean.BeanUtils;
import com.xzzn.ems.domain.*; import com.xzzn.ems.domain.EmsStrategy;
import com.xzzn.ems.domain.EmsStrategyRunning;
import com.xzzn.ems.domain.EmsStrategyTimeConfig;
import com.xzzn.ems.domain.MqttSyncLog;
import com.xzzn.ems.domain.vo.SyncStrategyTimeConfigVo; import com.xzzn.ems.domain.vo.SyncStrategyTimeConfigVo;
import com.xzzn.ems.mapper.*; import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper;
import com.xzzn.ems.mapper.EmsStrategyMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.mapper.MqttSyncLogMapper;
import com.xzzn.framework.web.service.MqttPublisher; import com.xzzn.framework.web.service.MqttPublisher;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory; import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap; import org.springframework.cglib.beans.BeanMap;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.*;
/** /**
* 策略时间配置同步 * 策略时间配置同步
* 云端 - 本地 * 云端 - 本地
@ -27,7 +37,7 @@ import java.util.*;
@Component @Component
public class StrategyTimeConfigSyncAspect { public class StrategyTimeConfigSyncAspect {
private static final Log logger = LogFactory.getLog(StrategyTimeConfigSyncAspect.class); private static final Logger logger = LoggerFactory.getLogger(StrategyTimeConfigSyncAspect.class);
@Autowired @Autowired
private MqttPublisher mqttPublisher; private MqttPublisher mqttPublisher;

View File

@ -13,8 +13,9 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
* @author xzzn * @author xzzn
*/ */
@Configuration @Configuration
// 表示通过aop框架暴露该代理对象,AopContext能够访问 // proxyTargetClass = true表示使用cglib代理false表示jdk动态代理
@EnableAspectJAutoProxy(exposeProxy = true) // exposeProxy = true表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
// 指定要扫描的Mapper类的包的路径 // 指定要扫描的Mapper类的包的路径
@MapperScan("com.xzzn.**.mapper") @MapperScan("com.xzzn.**.mapper")
public class ApplicationConfig public class ApplicationConfig

View File

@ -1,38 +0,0 @@
package com.xzzn.framework.config;
import com.xzzn.framework.manager.ModbusConnectionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModbusConfig {
@Value("${modbus.pool.max-total:20}")
private int maxTotal;
@Value("${modbus.pool.max-idle:10}")
private int maxIdle;
@Value("${modbus.pool.min-idle:3}")
private int minIdle;
@Value("${modbus.pool.max-wait:3000}")
private long maxWaitMillis;
@Value("${modbus.pool.time-between-eviction-runs:30000}")
private long timeBetweenEvictionRunsMillis;
@Value("${modbus.pool.min-evictable-idle-time:60000}")
private long minEvictableIdleTimeMillis;
public ModbusConnectionManager modbusConnectionManager() {
ModbusConnectionManager manager = new ModbusConnectionManager();
manager.setMaxTotal(maxTotal);
manager.setMaxIdle(maxIdle);
manager.setMinIdle(minIdle);
manager.setMaxWaitMillis(maxWaitMillis);
manager.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
manager.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
return manager;
}
}

View File

@ -1,274 +0,0 @@
package com.xzzn.framework.manager;
import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.service.IEmsAlarmRecordsService;
import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.IEmsEnergyPriceConfigService;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;
@Component
public class ModbusConnectionManager implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(ModbusConnectionManager.class);
private final Map<Integer, ModbusConnectionWrapper> connectionPool = new ConcurrentHashMap<>();
// 连接池配置参数
private int maxTotal = 20;
private int maxIdle = 10;
private int minIdle = 3;
private long maxWaitMillis = 3000;
private long timeBetweenEvictionRunsMillis = 30000;
private long minEvictableIdleTimeMillis = 60000;
private ScheduledExecutorService scheduler;
@Autowired
private EmsDevicesSettingMapper deviceRepo;
@Autowired
private IEmsAlarmRecordsService iEmsAlarmRecordsService;
@Autowired
private IEmsEnergyPriceConfigService iEmsEnergyPriceConfigService;
@Autowired
private IEmsDeviceSettingService iEmsDeviceSettingService;
@Override
public void run(ApplicationArguments args) throws Exception {
init();
}
public void init() {
// 启动心跳检测线程
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(this::heartbeatCheck, 1, 5, TimeUnit.MINUTES);
logger.info("Modbus连接管理器已初始化");
// 初始数据工作
initData();
}
private void initData() {
// 初始化-设备信息
iEmsDeviceSettingService.initDeviceInfo();
// 初始化-告警数据
iEmsAlarmRecordsService.initAlarmMatchInfo();
// 初始化当月电价
iEmsEnergyPriceConfigService.initCurrentMonthPrice();
}
/**
* 获取连接(带自动创建和缓存)
*/
public ModbusConnectionWrapper getConnection(EmsDevicesSetting device) throws Exception {
return connectionPool.compute(Math.toIntExact(device.getId()), (id, wrapper) -> {
try {
if (wrapper == null || !wrapper.isActive()) {
if (connectionPool.size() >= maxTotal) {
evictIdleConnection();
}
logger.info("创建新连接: {}", device);
return new ModbusConnectionWrapper(createRawConnection(device));
}
wrapper.updateLastAccess();
return wrapper;
} catch (Exception e) {
throw new RuntimeException("连接创建失败: " + device.getId(), e);
}
});
}
/**
* 创建原始Modbus连接
*/
private TCPMasterConnection createRawConnection(EmsDevicesSetting device) throws Exception {
try {
InetAddress addr = InetAddress.getByName("10.1.0.230");
TCPMasterConnection connection = new TCPMasterConnection(addr);
connection.setPort(502);
connection.setTimeout(5000);
connection.connect();
return connection;
} catch (Exception e) {
logger.error("创建Modbus连接失败: {}", device, e);
throw e;
}
}
/**
* 心跳检测
*/
private void heartbeatCheck() {
logger.info("开始监控Modbus连接池状态当前连接数: {}", connectionPool.size());
// 步骤1获取所有活跃设备列表与轮询逻辑共用同一批设备
List<EmsDevicesSetting> activeDevices = null;
if (activeDevices == null || activeDevices.isEmpty()) {
logger.warn("无活跃设备,心跳检测仅清理无效连接");
}
// 步骤2清理无效连接遍历连接池移除已失效的连接
List<Integer> invalidDeviceIds = new ArrayList<>();
connectionPool.forEach((deviceId, wrapper) -> {
try {
if (!wrapper.isActive()) {
logger.info("连接{}已失效,移除连接", deviceId);
invalidDeviceIds.add(deviceId);
wrapper.close();
}
} catch (Exception e) {
logger.error("心跳检测异常: {}", deviceId, e);
}
});
// 批量移除无效连接(避免边遍历边修改)
invalidDeviceIds.forEach(connectionPool::remove);
logger.debug("移除无效连接后,连接池大小: {}", connectionPool.size());
// 步骤3补充关键设备的连接优先保障活跃设备的连接存在
if (!activeDevices.isEmpty()) {
// 3.1 先为所有活跃设备预加载连接(确保需要轮询的设备有连接)
preloadCriticalConnection(activeDevices);
// 3.2 若连接数仍不足minIdle补充额外连接可选避免连接池过小
int currentSize = connectionPool.size();
if (currentSize < minIdle) {
logger.info("连接数{}不足最小空闲数{},补充额外连接", currentSize, minIdle);
// 从活跃设备中选未创建连接的设备补充(避免重复创建)
List<EmsDevicesSetting> needMoreDevices = activeDevices.stream()
.filter(device -> !connectionPool.containsKey(Math.toIntExact(device.getId())))
.limit(minIdle - currentSize) // 只补充差额
.collect(Collectors.toList());
preloadCriticalConnection(needMoreDevices); // 复用预加载方法
}
}
}
/**
* 预加载关键连接
*/
private void preloadCriticalConnection(List<EmsDevicesSetting> devices) {
// 简化示例,不实现具体逻辑
logger.info("预加载连接: 连接池当前大小={}, 最小空闲={}", connectionPool.size(), minIdle);
devices.forEach(device -> {
try {
Integer deviceId = Math.toIntExact(device.getId());
if (!connectionPool.containsKey(deviceId)) {
getConnection(device); // 复用已有创建逻辑
}
} catch (Exception e) {
logger.warn("预加载设备{}连接失败", device.getId(), e);
}
});
}
/**
* 移除最久未使用的空闲连接
*/
private void evictIdleConnection() {
if (connectionPool.isEmpty()) {
return;
}
ModbusConnectionWrapper oldestWrapper = null;
long oldestAccessTime = Long.MAX_VALUE;
for (ModbusConnectionWrapper wrapper : connectionPool.values()) {
if (wrapper.isActive() && wrapper.getLastAccessTime() < oldestAccessTime) {
oldestAccessTime = wrapper.getLastAccessTime();
oldestWrapper = wrapper;
}
}
if (oldestWrapper != null) {
logger.info("移除空闲连接: {}", oldestWrapper.getConnection());
connectionPool.values().remove(oldestWrapper);
oldestWrapper.close();
}
}
// 移除指定设备连接
public void removeConnection(Integer deviceId) {
ModbusConnectionWrapper wrapper = connectionPool.remove(deviceId);
if (wrapper != null) {
wrapper.close(); // 双重保障,确保连接关闭
logger.info("连接池主动移除设备{}的连接", deviceId);
}
}
/**
* 判断是否应该移除空连接池
*/
private boolean shouldRemoveEmptyPool(GenericObjectPool<?> pool) {
// 可根据配置或逻辑决定是否移除空连接池
// 这里简单实现为当连接池数量超过最大值时移除
return connectionPool.size() > maxTotal;
}
/**
* 关闭连接
*/
private void closeConnection(TCPMasterConnection connection) {
try {
if (connection != null && connection.isConnected()) {
connection.close();
}
} catch (Exception e) {
logger.error("关闭Modbus连接失败", e);
}
}
// 容器销毁时关闭线程池
@PreDestroy
public void destroy() {
if (scheduler != null) {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
}
}
}
// Getters and Setters
public void setMaxTotal(int maxTotal) {
this.maxTotal = maxTotal;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public void setMaxWaitMillis(long maxWaitMillis) {
this.maxWaitMillis = maxWaitMillis;
}
public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
}

View File

@ -1,81 +0,0 @@
package com.xzzn.framework.manager;
import com.ghgande.j2mod.modbus.net.SerialConnection;
import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class ModbusConnectionWrapper {
private static final Logger logger = LoggerFactory.getLogger(ModbusConnectionWrapper.class);
private static final AtomicInteger COUNTER = new AtomicInteger(0);
private final Object connection;
private final int connectionId;
private volatile long lastAccessTime;
private volatile boolean active = true;
public ModbusConnectionWrapper(Object connection) {
this.connection = connection;
this.connectionId = COUNTER.incrementAndGet();
this.lastAccessTime = System.currentTimeMillis();
logger.info("创建连接包装: {}", this);
}
public boolean isActive() {
if (!active) return false;
try {
// 检查连接是否物理上有效
if (connection instanceof TCPMasterConnection) {
return ((TCPMasterConnection) connection).isConnected();
} else if (connection instanceof SerialConnection) {
return ((SerialConnection) connection).isOpen();
}
} catch (Exception e) {
logger.error("连接状态检查失败: {}", connectionId, e);
return false;
}
// 默认检查空闲时间
return System.currentTimeMillis() - lastAccessTime < 300000; // 5分钟
}
public void updateLastAccess() {
this.lastAccessTime = System.currentTimeMillis();
}
public Object getConnection() {
return connection;
}
public void close() {
try {
logger.info("关闭连接: {}", this);
if (connection instanceof TCPMasterConnection) {
((TCPMasterConnection) connection).close();
} else if (connection instanceof SerialConnection) {
((SerialConnection) connection).close();
}
} catch (Exception e) {
logger.error("关闭连接失败: {}", connectionId, e);
} finally {
this.active = false;
}
}
public long getLastAccessTime() {
return lastAccessTime;
}
@Override
public String toString() {
return "ModbusConnectionWrapper{" +
"connectionId=" + connectionId +
", active=" + active +
", lastAccessTime=" + lastAccessTime +
'}';
}
}

View File

@ -3,21 +3,37 @@ package com.xzzn.framework.manager;
import com.xzzn.ems.service.IEmsAlarmRecordsService; import com.xzzn.ems.service.IEmsAlarmRecordsService;
import org.eclipse.paho.client.mqttv3.*; import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner; import org.springframework.boot.ApplicationRunner;
import org.springframework.context.SmartLifecycle; import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@Component @Component
public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, MqttCallback { public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, MqttCallbackExtended {
private static final Logger log = LoggerFactory.getLogger(MqttLifecycleManager.class);
private static final long RECONNECT_DELAY_SECONDS = 5;
private final MqttConnectOptions connectOptions; private final MqttConnectOptions connectOptions;
private final IEmsAlarmRecordsService iEmsAlarmRecordsService; private final IEmsAlarmRecordsService iEmsAlarmRecordsService;
private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r);
thread.setName("mqtt-reconnect");
thread.setDaemon(true);
return thread;
});
private final AtomicBoolean reconnectScheduled = new AtomicBoolean(false);
private volatile ScheduledFuture<?> reconnectFuture;
private MqttClient mqttClient; private MqttClient mqttClient;
private volatile boolean running = false; private volatile boolean running = false;
@ -41,7 +57,9 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle,
if (running) return; if (running) return;
try { try {
String clientId = connectOptions.getUserName() + "-" + System.currentTimeMillis(); String prefix = connectOptions.getUserName() == null || connectOptions.getUserName().isEmpty()
? "mqtt-client" : connectOptions.getUserName();
String clientId = prefix + "-" + System.currentTimeMillis();
mqttClient = new MqttClient( mqttClient = new MqttClient(
connectOptions.getServerURIs()[0], connectOptions.getServerURIs()[0],
clientId, clientId,
@ -51,27 +69,28 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle,
mqttClient.setCallback(this); mqttClient.setCallback(this);
mqttClient.connect(connectOptions); mqttClient.connect(connectOptions);
// 重连后自动重新订阅
resubscribeAll(); resubscribeAll();
running = true; running = true;
System.out.println("MQTT client connected to: " + connectOptions.getServerURIs()[0]); log.info("MQTT client connected to: {}", connectOptions.getServerURIs()[0]);
} catch (MqttException e) { } catch (MqttException e) {
System.err.println("MQTT connection failed: " + e.getMessage()); running = false;
// 添加重试逻辑 log.error("MQTT connection failed: {}", e.getMessage(), e);
scheduleReconnect();
} }
} }
@Override @Override
public void stop() { public void stop() {
cancelReconnectTask();
if (mqttClient != null && mqttClient.isConnected()) { if (mqttClient != null && mqttClient.isConnected()) {
try { try {
mqttClient.disconnect(); mqttClient.disconnect();
mqttClient.close(); mqttClient.close();
} catch (MqttException e) { } catch (MqttException e) {
System.err.println("Error disconnecting MQTT client: " + e.getMessage()); log.warn("Error disconnecting MQTT client: {}", e.getMessage(), e);
} }
} }
reconnectExecutor.shutdownNow();
running = false; running = false;
} }
@ -83,9 +102,17 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle,
// MQTT 回调方法 // MQTT 回调方法
@Override @Override
public void connectionLost(Throwable cause) { public void connectionLost(Throwable cause) {
System.err.println("MQTT connection lost: " + cause.getMessage()); log.warn("MQTT connection lost: {}", cause == null ? "unknown" : cause.getMessage(), cause);
running = false; running = false;
// 自动重连由 MqttConnectOptions 处理 scheduleReconnect();
}
@Override
public void connectComplete(boolean reconnect, String serverURI) {
running = true;
cancelReconnectTask();
log.info("MQTT connection complete, reconnect: {}, serverURI: {}", reconnect, serverURI);
resubscribeAll();
} }
@Override @Override
@ -106,14 +133,16 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle,
try { try {
if (mqttClient != null && mqttClient.isConnected()) { if (mqttClient != null && mqttClient.isConnected()) {
mqttClient.subscribe(topic, qos); mqttClient.subscribe(topic, qos);
log.info("MQTT subscribe success, topic: {}, qos: {}", topic, qos);
} else {
log.warn("MQTT subscribe deferred, client not connected, topic: {}", topic);
} }
subscriptions.put(topic, new SubscriptionInfo(listener, qos)); subscriptions.put(topic, new SubscriptionInfo(listener, qos));
} catch (MqttException e) { } catch (MqttException e) {
System.err.println("Subscribe failed: " + e.getMessage()); log.error("Subscribe failed, topic: {}, err: {}", topic, e.getMessage(), e);
// 订阅失败-增加告警
iEmsAlarmRecordsService.addSubFailedAlarmRecord(topic); iEmsAlarmRecordsService.addSubFailedAlarmRecord(topic);
scheduleReconnect();
} }
// 订阅成功了-校验是否存在未处理或者处理中的订阅失败信息
iEmsAlarmRecordsService.checkFailedRecord(topic); iEmsAlarmRecordsService.checkFailedRecord(topic);
} }
@ -135,12 +164,52 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle,
subscriptions.forEach((topic, info) -> { subscriptions.forEach((topic, info) -> {
try { try {
mqttClient.subscribe(topic, info.getQos()); mqttClient.subscribe(topic, info.getQos());
log.info("MQTT resubscribe success, topic: {}, qos: {}", topic, info.getQos());
} catch (MqttException e) { } catch (MqttException e) {
System.err.println("Resubscribe failed for topic: " + topic); log.error("Resubscribe failed for topic: {}, err: {}", topic, e.getMessage(), e);
} }
}); });
} }
private void scheduleReconnect() {
if (mqttClient == null || reconnectExecutor.isShutdown()) {
return;
}
if (!reconnectScheduled.compareAndSet(false, true)) {
return;
}
reconnectFuture = reconnectExecutor.scheduleWithFixedDelay(() -> {
if (mqttClient == null) {
cancelReconnectTask();
return;
}
if (mqttClient.isConnected()) {
cancelReconnectTask();
return;
}
try {
log.info("MQTT reconnecting...");
mqttClient.connect(connectOptions);
running = true;
cancelReconnectTask();
resubscribeAll();
log.info("MQTT reconnect success.");
} catch (MqttException e) {
running = false;
log.warn("MQTT reconnect failed: {}", e.getMessage());
}
}, RECONNECT_DELAY_SECONDS, RECONNECT_DELAY_SECONDS, TimeUnit.SECONDS);
}
private void cancelReconnectTask() {
reconnectScheduled.set(false);
ScheduledFuture<?> future = reconnectFuture;
if (future != null && !future.isCancelled()) {
future.cancel(false);
}
}
// 订阅信息内部类 // 订阅信息内部类
private static class SubscriptionInfo { private static class SubscriptionInfo {
private final IMqttMessageListener listener; private final IMqttMessageListener listener;
@ -159,4 +228,4 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle,
return qos; return qos;
} }
} }
} }

View File

@ -1,279 +0,0 @@
package com.xzzn.framework.web.service;
import com.ghgande.j2mod.modbus.ModbusException;
import com.ghgande.j2mod.modbus.ModbusIOException;
import com.ghgande.j2mod.modbus.io.ModbusSerialTransaction;
import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction;
import com.ghgande.j2mod.modbus.msg.ReadInputRegistersRequest;
import com.ghgande.j2mod.modbus.msg.ReadInputRegistersResponse;
import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersRequest;
import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterRequest;
import com.ghgande.j2mod.modbus.net.SerialConnection;
import com.ghgande.j2mod.modbus.net.TCPMasterConnection;
import com.ghgande.j2mod.modbus.procimg.Register;
import com.ghgande.j2mod.modbus.procimg.SimpleRegister;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
/**
* Modbus操作服务添加重试机制
*/
@Service
public class ModbusService {
private static final Logger logger = LoggerFactory.getLogger(ModbusService.class);
@Retryable(
value = {ModbusException.class}, // 仅对自定义Modbus异常重试
maxAttempts = 3, // 最大重试3次1次原始调用 + 2次重试
backoff = @Backoff(delay = 1000, multiplier = 2) // 退避策略1s → 2s → 4s
)
@CircuitBreaker(name = "modbusOperation", fallbackMethod = "readRegistersFallback")
public int[] readHoldingRegisters(Object connection, int startAddr, int count) throws ModbusException {
try {
if (connection instanceof TCPMasterConnection) {
return readTcpRegisters((TCPMasterConnection) connection, startAddr, count);
} else if (connection instanceof SerialConnection) {
return readRtuRegisters((SerialConnection) connection, startAddr, count);
}
throw new IllegalArgumentException("不支持的连接类型: " + connection.getClass().getName());
} catch (ModbusIOException e) {
throw new ModbusException("通信故障", e);
} catch (Exception e) {
throw new ModbusException("系统错误", e);
}
}
private int[] readRtuRegisters(SerialConnection connection, int startAddr, int count) throws ModbusException {
if (!connection.isOpen()) {
throw new ModbusIOException("RTU连接未建立");
}
ReadInputRegistersRequest request = new ReadInputRegistersRequest(startAddr, count);
ModbusSerialTransaction transaction = new ModbusSerialTransaction(connection);
transaction.setRequest(request);
transaction.setRetries(2);
try {
transaction.execute();
ReadInputRegistersResponse response = (ReadInputRegistersResponse) transaction.getResponse();
if (response == null) {
throw new ModbusException("RTU响应为空");
}
return parseRegisters(response);
} catch (ModbusException e) {
logger.error("读取RTU寄存器失败: {}", e.getMessage());
throw e;
}
}
private int[] readTcpRegisters(TCPMasterConnection conn, int start, int count) throws ModbusException {
// 验证连接是否已建立
if (!conn.isConnected()) {
throw new ModbusIOException("TCP连接未建立");
}
// 使用正确的功能码03 - 读取保持寄存器ReadHoldingRegistersRequest
ReadInputRegistersRequest request = new ReadInputRegistersRequest(start, count);
ModbusTCPTransaction transaction = new ModbusTCPTransaction(conn);
transaction.setRequest(request);
// 设置超时避免长时间阻塞
transaction.setRetries(2);
try {
transaction.execute();
ReadInputRegistersResponse response = (ReadInputRegistersResponse) transaction.getResponse();
if (response == null) {
throw new ModbusException("Modbus异常响应: " + response.getMessage());
}
// 正确解析寄存器值
return parseRegisters(response);
} catch (ModbusException e) {
// 记录详细错误信息
logger.error("读取TCP寄存器失败: {}", e.getMessage());
throw e;
}
}
/**
* 解析Modbus响应中的寄存器值
*/
private int[] parseRegisters(ReadInputRegistersResponse response) {
int byteCount = response.getByteCount();
int[] result = new int[byteCount / 2];
for (int i = 0; i < result.length; i++) {
// 转换为无符号整数
result[i] = response.getRegisterValue(i) & 0xFFFF;
}
return result;
}
/**
* 熔断降级方法
*/
public int[] readRegistersFallback(Object connection, int startAddr, int count, Exception e) {
logger.warn("Modbus操作降级原因: {}),返回空数据", e.getMessage());
return new int[0];
}
/**
* 写入单个寄存器支持TCP/RTU
* @param connection 连接对象TCPMasterConnection 或 SerialConnection
* @param registerAddr 寄存器地址
* @param value 要写入的值16位整数
*/
@Retryable(
value = {ModbusException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
@CircuitBreaker(name = "modbusOperation", fallbackMethod = "writeRegisterFallback")
public boolean writeSingleRegister(Object connection, int registerAddr, int value) throws ModbusException {
try {
if (connection instanceof TCPMasterConnection) {
return writeTcpSingleRegister((TCPMasterConnection) connection, registerAddr, value);
} else if (connection instanceof SerialConnection) {
return writeRtuSingleRegister((SerialConnection) connection, registerAddr, value);
}
throw new IllegalArgumentException("不支持的连接类型: " + connection.getClass().getName());
} catch (ModbusIOException e) {
throw new ModbusException("写入通信故障", e);
} catch (Exception e) {
throw new ModbusException("写入系统错误", e);
}
}
/**
* 写入多个寄存器支持TCP/RTU
* @param connection 连接对象
* @param startAddr 起始寄存器地址
* @param values 要写入的值数组每个值为16位整数
*/
@Retryable(
value = {ModbusException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
@CircuitBreaker(name = "modbusOperation", fallbackMethod = "writeRegisterFallback")
public boolean writeMultipleRegisters(Object connection, int startAddr, int[] values) throws ModbusException {
try {
if (connection instanceof TCPMasterConnection) {
return writeTcpMultipleRegisters((TCPMasterConnection) connection, startAddr, values);
} else if (connection instanceof SerialConnection) {
return writeRtuMultipleRegisters((SerialConnection) connection, startAddr, values);
}
throw new IllegalArgumentException("不支持的连接类型: " + connection.getClass().getName());
} catch (ModbusIOException e) {
throw new ModbusException("写入通信故障", e);
} catch (Exception e) {
throw new ModbusException("写入系统错误", e);
}
}
// ==================== TCP写入实现 ====================
private boolean writeTcpSingleRegister(TCPMasterConnection conn, int registerAddr, int value) throws ModbusException {
if (!conn.isConnected()) {
throw new ModbusIOException("TCP连接未建立无法写入");
}
// 创建写入单个寄存器的请求功能码06
WriteSingleRegisterRequest request = new WriteSingleRegisterRequest(registerAddr, new SimpleRegister(value));
ModbusTCPTransaction transaction = new ModbusTCPTransaction(conn);
transaction.setRequest(request);
transaction.setRetries(2);
try {
transaction.execute();
logger.info("TCP写入单个寄存器成功地址:{},值:{}", registerAddr, value);
return true;
} catch (ModbusException e) {
logger.error("TCP写入单个寄存器失败地址:{},值:{}", registerAddr, value, e);
throw e;
}
}
private boolean writeTcpMultipleRegisters(TCPMasterConnection conn, int startAddr, int[] values) throws ModbusException {
if (!conn.isConnected()) {
throw new ModbusIOException("TCP连接未建立无法写入");
}
// 转换值数组为寄存器数组
Register[] registers = new Register[values.length];
for (int i = 0; i < values.length; i++) {
registers[i] = new SimpleRegister(values[i]);
}
// 创建写入多个寄存器的请求功能码16
WriteMultipleRegistersRequest request = new WriteMultipleRegistersRequest(startAddr, registers);
ModbusTCPTransaction transaction = new ModbusTCPTransaction(conn);
transaction.setRequest(request);
transaction.setRetries(2);
try {
transaction.execute();
logger.info("TCP写入多个寄存器成功起始地址:{},数量:{}", startAddr, values.length);
return true;
} catch (ModbusException e) {
logger.error("TCP写入多个寄存器失败起始地址:{}", startAddr, e);
throw e;
}
}
// ==================== RTU写入实现 ====================
private boolean writeRtuSingleRegister(SerialConnection connection, int registerAddr, int value) throws ModbusException {
if (!connection.isOpen()) {
throw new ModbusIOException("RTU串口未打开请先建立连接");
}
WriteSingleRegisterRequest request = new WriteSingleRegisterRequest(registerAddr, new SimpleRegister(value));
ModbusSerialTransaction transaction = new ModbusSerialTransaction(connection);
transaction.setRequest(request);
transaction.setRetries(2);
try {
transaction.execute();
logger.info("RTU写入单个寄存器成功地址:{},值:{}", registerAddr, value);
return true;
} catch (ModbusException e) {
logger.error("RTU写入单个寄存器失败地址:{},值:{}", registerAddr, value, e);
throw e;
}
}
private boolean writeRtuMultipleRegisters(SerialConnection connection, int startAddr, int[] values) throws ModbusException {
if (!connection.isOpen()) {
throw new ModbusIOException("RTU串口未打开请先建立连接");
}
Register[] registers = new Register[values.length];
for (int i = 0; i < values.length; i++) {
registers[i] = new SimpleRegister(values[i]);
}
WriteMultipleRegistersRequest request = new WriteMultipleRegistersRequest(startAddr, registers);
ModbusSerialTransaction transaction = new ModbusSerialTransaction(connection);
transaction.setRequest(request);
transaction.setRetries(2);
try {
transaction.execute();
logger.info("RTU写入多个寄存器成功起始地址:{},数量:{}", startAddr, values.length);
return true;
} catch (ModbusException e) {
logger.error("RTU写入多个寄存器失败起始地址:{}", startAddr, e);
throw e;
}
}
// ==================== 写入操作的降级方法 ====================
public boolean writeRegisterFallback(Object connection, int addr, int value, Exception e) {
logger.warn("写入单个寄存器降级(原因: {}),地址:{}", e.getMessage(), addr);
return false;
}
public boolean writeRegisterFallback(Object connection, int startAddr, int[] values, Exception e) {
logger.warn("写入多个寄存器降级(原因: {}),起始地址:{}", e.getMessage(), startAddr);
return false;
}
}

View File

@ -1,7 +1,6 @@
package com.xzzn.generator.controller; package com.xzzn.generator.controller;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -18,10 +17,6 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement;
import com.xzzn.common.annotation.Log; import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController; import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult; import com.xzzn.common.core.domain.AjaxResult;
@ -29,7 +24,6 @@ import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.core.text.Convert; import com.xzzn.common.core.text.Convert;
import com.xzzn.common.enums.BusinessType; import com.xzzn.common.enums.BusinessType;
import com.xzzn.common.utils.SecurityUtils; import com.xzzn.common.utils.SecurityUtils;
import com.xzzn.common.utils.sql.SqlUtil;
import com.xzzn.generator.config.GenConfig; import com.xzzn.generator.config.GenConfig;
import com.xzzn.generator.domain.GenTable; import com.xzzn.generator.domain.GenTable;
import com.xzzn.generator.domain.GenTableColumn; import com.xzzn.generator.domain.GenTableColumn;
@ -121,43 +115,6 @@ public class GenController extends BaseController
return success(); return success();
} }
/**
* 创建表结构(保存)
*/
@PreAuthorize("@ss.hasRole('admin')")
@Log(title = "创建表", businessType = BusinessType.OTHER)
@PostMapping("/createTable")
public AjaxResult createTableSave(String sql)
{
try
{
SqlUtil.filterKeyword(sql);
List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql);
List<String> tableNames = new ArrayList<>();
for (SQLStatement sqlStatement : sqlStatements)
{
if (sqlStatement instanceof MySqlCreateTableStatement)
{
MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement;
if (genTableService.createTable(createTableStatement.toString()))
{
String tableName = createTableStatement.getTableName().replaceAll("`", "");
tableNames.add(tableName);
}
}
}
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()]));
String operName = SecurityUtils.getUsername();
genTableService.importGenTable(tableList, operName);
return AjaxResult.success();
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
return AjaxResult.error("创建表结构异常");
}
}
/** /**
* 修改保存代码生成业务 * 修改保存代码生成业务
*/ */
@ -260,4 +217,4 @@ public class GenController extends BaseController
response.setContentType("application/octet-stream; charset=UTF-8"); response.setContentType("application/octet-stream; charset=UTF-8");
IOUtils.write(data, response.getOutputStream()); IOUtils.write(data, response.getOutputStream());
} }
} }

View File

@ -81,11 +81,4 @@ public interface GenTableMapper
*/ */
public int deleteGenTableByIds(Long[] ids); public int deleteGenTableByIds(Long[] ids);
/**
* 创建表
*
* @param sql 表结构
* @return 结果
*/
public int createTable(String sql);
} }

View File

@ -149,18 +149,6 @@ public class GenTableServiceImpl implements IGenTableService
genTableColumnMapper.deleteGenTableColumnByIds(tableIds); genTableColumnMapper.deleteGenTableColumnByIds(tableIds);
} }
/**
* 创建表
*
* @param sql 创建表语句
* @return 结果
*/
@Override
public boolean createTable(String sql)
{
return genTableMapper.createTable(sql) == 0;
}
/** /**
* 导入表结构 * 导入表结构
* *
@ -528,4 +516,4 @@ public class GenTableServiceImpl implements IGenTableService
} }
return genPath + File.separator + VelocityUtils.getFileName(template, table); return genPath + File.separator + VelocityUtils.getFileName(template, table);
} }
} }

View File

@ -66,14 +66,6 @@ public interface IGenTableService
*/ */
public void deleteGenTableByIds(Long[] tableIds); public void deleteGenTableByIds(Long[] tableIds);
/**
* 创建表
*
* @param sql 创建表语句
* @return 结果
*/
public boolean createTable(String sql);
/** /**
* 导入表结构 * 导入表结构
* *

View File

@ -171,10 +171,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
) )
</insert> </insert>
<update id="createTable">
${sql}
</update>
<update id="updateGenTable" parameterType="GenTable"> <update id="updateGenTable" parameterType="GenTable">
update gen_table update gen_table
<set> <set>
@ -207,4 +203,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach> </foreach>
</delete> </delete>
</mapper> </mapper>

View File

@ -0,0 +1,33 @@
package com.xzzn.quartz.config;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.PreDestroy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Modbus操作执行器配置
* 所有Modbus读写操作必须通过此单线程执行器串行执行避免并发访问导致通讯故障
*/
@Configuration
public class ModbusExecutorConfig {
private final ExecutorService modbusExecutor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "modbus-io-thread");
t.setDaemon(false);
return t;
});
@Bean(name = "modbusExecutor")
public ExecutorService modbusExecutor() {
return modbusExecutor;
}
@PreDestroy
public void shutdown() {
modbusExecutor.shutdown();
}
}

View File

@ -0,0 +1,35 @@
package com.xzzn.quartz.task;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.service.impl.DeviceDataProcessServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("calcPointTask")
public class CalcPointTask {
private static final Logger log = LoggerFactory.getLogger(CalcPointTask.class);
@Autowired
private DeviceDataProcessServiceImpl deviceDataProcessService;
public void syncAllSites() {
log.info("开始执行计算点轮询任务-全站点");
deviceDataProcessService.syncCalcPointDataFromInfluxByQuartz();
log.info("结束执行计算点轮询任务-全站点");
}
public void syncBySite(String siteId) {
if (StringUtils.isBlank(siteId)) {
log.warn("计算点轮询任务参数为空,转为全站点执行");
syncAllSites();
return;
}
String normalizedSiteId = siteId.trim();
log.info("开始执行计算点轮询任务-单站点, siteId: {}", normalizedSiteId);
deviceDataProcessService.syncCalcPointDataFromInfluxByQuartz(normalizedSiteId);
log.info("结束执行计算点轮询任务-单站点, siteId: {}", normalizedSiteId);
}
}

View File

@ -0,0 +1,35 @@
package com.xzzn.quartz.task;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.service.impl.DeviceDataProcessServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("dailyChargeDataTask")
public class DailyChargeDataTask {
private static final Logger log = LoggerFactory.getLogger(DailyChargeDataTask.class);
@Autowired
private DeviceDataProcessServiceImpl deviceDataProcessService;
public void syncAllSites() {
log.info("开始执行每日充放电数据轮询任务-全站点");
deviceDataProcessService.syncDailyChargeDataFromInfluxByQuartz();
log.info("结束执行每日充放电数据轮询任务-全站点");
}
public void syncBySite(String siteId) {
if (StringUtils.isBlank(siteId)) {
log.warn("每日充放电数据轮询任务参数为空,转为全站点执行");
syncAllSites();
return;
}
String normalizedSiteId = siteId.trim();
log.info("开始执行每日充放电数据轮询任务-单站点, siteId: {}", normalizedSiteId);
deviceDataProcessService.syncDailyChargeDataFromInfluxByQuartz(normalizedSiteId);
log.info("结束执行每日充放电数据轮询任务-单站点, siteId: {}", normalizedSiteId);
}
}

View File

@ -2,37 +2,45 @@ package com.xzzn.quartz.task;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.serotonin.modbus4j.ModbusMaster; import com.serotonin.modbus4j.ModbusMaster;
import com.xzzn.common.constant.RedisKeyConstants; import com.xzzn.common.constant.RedisKeyConstants;
import com.xzzn.common.core.modbus.ModbusProcessor; import com.xzzn.common.core.modbus.ModbusProcessor;
import com.xzzn.common.core.modbus.domain.DeviceConfig; import com.xzzn.common.core.modbus.domain.DeviceConfig;
import com.xzzn.common.core.modbus.domain.TagConfig;
import com.xzzn.common.core.redis.RedisCache; import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.utils.DateUtils; import com.xzzn.common.enums.DeviceRunningStatus;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsPointConfig;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsPointConfigMapper;
import com.xzzn.ems.service.IEmsAlarmRecordsService; import com.xzzn.ems.service.IEmsAlarmRecordsService;
import com.xzzn.ems.service.impl.DeviceDataProcessServiceImpl; import com.xzzn.ems.service.impl.DeviceDataProcessServiceImpl;
import com.xzzn.framework.manager.MqttLifecycleManager;
import com.xzzn.framework.web.service.MqttPublisher; import com.xzzn.framework.web.service.MqttPublisher;
import com.xzzn.quartz.config.ScheduledTask;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
/** /**
* 轮询设备-通过modbus协议读取数据 * 轮询设备-通过modbus协议读取数据
@ -40,165 +48,439 @@ import org.springframework.stereotype.Component;
@Component("modbusPoller") @Component("modbusPoller")
public class ModbusPoller { public class ModbusPoller {
private static final Logger log = LoggerFactory.getLogger(ModbusPoller.class); private static final Logger log = LoggerFactory.getLogger(ModbusPoller.class);
private static final int SITE_DEVICE_OFFLINE_THRESHOLD = 6;
private final MqttLifecycleManager mqttLifecycleManager; private final Map<String, Integer> deviceFailureCounts = new ConcurrentHashMap<>();
private final ScheduledTask scheduledTask; private final AtomicBoolean polling = new AtomicBoolean(false);
private final ObjectMapper objectMapper = new ObjectMapper();
@Resource(name = "modbusExecutor")
private ExecutorService modbusExecutor;
@Autowired @Autowired
private ModbusProcessor modbusProcessor; private ModbusProcessor modbusProcessor;
@Autowired @Autowired
private IEmsAlarmRecordsService iEmsAlarmRecordsService; private IEmsAlarmRecordsService iEmsAlarmRecordsService;
@Autowired @Autowired
private DeviceDataProcessServiceImpl deviceDataProcessServiceImpl; private DeviceDataProcessServiceImpl deviceDataProcessServiceImpl;
@Autowired @Autowired
private EmsDevicesSettingMapper emsDevicesSettingMapper;
@Autowired
private EmsPointConfigMapper emsPointConfigMapper;
@Autowired
private RedisCache redisCache; private RedisCache redisCache;
@Autowired @Autowired
private MqttPublisher mqttPublisher; private MqttPublisher mqttPublisher;
@Value("${mqtt.topic}") @Value("${mqtt.topic}")
private String topic; private String topic;
@Value("${mqtt.siteId}")
private String siteId;
@Autowired
public ModbusPoller(MqttLifecycleManager mqttLifecycleManager, ScheduledTask scheduledTask) {
this.mqttLifecycleManager = mqttLifecycleManager;
this.scheduledTask = scheduledTask;
}
public void pollAllDevices() { public void pollAllDevices() {
Path devicesDir = Paths.get(System.getProperty("user.dir"), "devices"); if (!polling.compareAndSet(false, true)) {
if (!Files.exists(devicesDir)) { log.warn("上一次轮询尚未完成,本次轮询跳过");
log.error("Devices目录不存在: {}", devicesDir); return;
}
List<PollingTask> pollingTasks = buildPollingTasks();
if (CollectionUtils.isEmpty(pollingTasks)) {
log.warn("未查询到可用的Modbus采集点位配置跳过本轮轮询");
polling.set(false);
return; return;
} }
List<Path> jsonFiles = null; // 按主机IP分组同一网关串行访问避免连接抖动
try { Map<String, List<PollingTask>> groupedByHost = pollingTasks.stream()
jsonFiles = Files.list(devicesDir) .collect(Collectors.groupingBy(
.filter(path -> path.toString().endsWith(".json")) task -> task.getDeviceConfig().getHost(),
.collect(Collectors.toList()); HashMap::new,
} catch (IOException e) { Collectors.toList()));
log.error("modbusPoller.loadConfigs 获取设备配置文件失败: {}", devicesDir, e);
return;
}
// 按 host:port 分组 Future<?> future = modbusExecutor.submit(() -> {
Map<String, List<DeviceConfig>> groupedConfigs = new HashMap<>(); for (Map.Entry<String, List<PollingTask>> entry : groupedByHost.entrySet()) {
for (Path filePath : jsonFiles) { String hostKey = entry.getKey();
DeviceConfig config = null; List<PollingTask> tasks = entry.getValue();
try { for (PollingTask task : tasks) {
config = objectMapper.readValue(filePath.toFile(), DeviceConfig.class);
} catch (IOException e) {
log.error("modbusPoller.loadConfigs 解析设备配置文件失败: {}", filePath, e);
continue;
}
if (config.isEnabled()) {
String key = config.getHost() + ":" + config.getPort();
groupedConfigs.computeIfAbsent(key, k -> new ArrayList<>()).add(config);
}
}
// 为每个 host:port 启动一个任务
for (Map.Entry<String, List<DeviceConfig>> entry : groupedConfigs.entrySet()) {
String groupKey = entry.getKey();
List<DeviceConfig> configs = entry.getValue();
// 取其中一个配置的时间间隔作为该组任务的执行周期
long interval = configs.get(0).getTime();
scheduledTask.startTask(groupKey, () -> {
for (DeviceConfig config : configs) {
try { try {
scheduledStart(config, null); scheduledStart(task.getSiteId(), task.getDeviceConfig());
// 每次读取后等待200ms给Modbus网关足够的处理时间
Thread.sleep(200);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
log.warn("Modbus轮询被中断");
return;
} catch (Exception e) { } catch (Exception e) {
log.error("采集设备数据异常: {}", config.getDeviceName(), e); log.error("采集设备数据异常: siteId={}, deviceId={}",
task.getSiteId(), task.getDeviceConfig().getDeviceNumber(), e);
} }
} }
}, interval); log.info("采集设备数据{}轮询任务执行完成", hostKey);
}
}
public void scheduledStart(DeviceConfig config, ModbusMaster master) {
try {
if (config.isEnabled()) {
log.info("Reading data from devices: {}", config.getDeviceName());
Map<String, Object> data = modbusProcessor.readDataFromDevice(config, master);
// 在这里处理采集到的数据
config.getTags().forEach(tag -> {
Object rawValue = data.get(tag.getKey());
if (rawValue != null) {
float value = 0;
if (rawValue instanceof Number) {
value = ((Number) rawValue).floatValue(); // 安全地转换为 float
} else {
log.error("tag:{},无法将数据转换为数字: {}", tag.getKey(), rawValue);
}
value = tag.getA() * value * value + tag.getK() * value + tag.getB();
int intValue = (int) value;
if (tag.getBit() != null) {
log.info("tag:{},bit:{},value:{}", tag.getKey(), tag.getBit(), value);
String binary = Integer.toBinaryString(intValue);
data.put(tag.getKey(), binary);
} else {
data.put(tag.getKey(), value);
}
} else {
data.put(tag.getKey(), rawValue);
log.warn("tag:{},数据为空: {}", tag.getKey(), rawValue);
}
});
log.info("Data from {}: {}", config.getDeviceName(), data);
String deviceNumber = config.getDeviceNumber();
//处理数据并发送MQTT消息、保存Redis数据和数据入库
processingData(data, deviceNumber);
} }
});
try {
future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Modbus轮询任务等待中断");
} catch (Exception e) { } catch (Exception e) {
log.error("设备数据采集异常: {}", config.getDeviceName(), e); log.error("Modbus轮询任务执行异常", e);
} finally {
polling.set(false);
} }
} }
private void processingData(Map<String, Object> data, String deviceNumber) { private List<PollingTask> buildPollingTasks() {
if (data == null || data.size() == 0) { List<EmsPointConfig> pointConfigs = emsPointConfigMapper.selectModbusCollectPointConfigs(null);
// 添加设备告警 if (CollectionUtils.isEmpty(pointConfigs)) {
iEmsAlarmRecordsService.addEmptyDataAlarmRecord(siteId, deviceNumber); return Collections.emptyList();
}
List<EmsDevicesSetting> allDevices = emsDevicesSettingMapper.selectEmsDevicesSettingList(null);
Map<String, EmsDevicesSetting> deviceMap = allDevices.stream()
.filter(Objects::nonNull)
.filter(device -> StringUtils.isNoneBlank(device.getSiteId(), device.getDeviceId()))
.collect(Collectors.toMap(
this::buildSiteDeviceKey,
device -> device,
(left, right) -> left));
Map<String, List<EmsPointConfig>> pointsByDevice = pointConfigs.stream()
.filter(Objects::nonNull)
.filter(point -> StringUtils.isNoneBlank(point.getSiteId(), point.getDeviceId()))
.collect(Collectors.groupingBy(
point -> point.getSiteId() + "_" + point.getDeviceId(),
HashMap::new,
Collectors.toList()));
List<PollingTask> tasks = new ArrayList<>();
for (Map.Entry<String, List<EmsPointConfig>> entry : pointsByDevice.entrySet()) {
String siteDeviceKey = entry.getKey();
EmsDevicesSetting device = deviceMap.get(siteDeviceKey);
if (device == null) {
log.warn("未找到设备连接配置,跳过采集: key={}", siteDeviceKey);
continue;
}
DeviceConfig deviceConfig = buildDeviceConfig(device, entry.getValue());
if (deviceConfig == null) {
continue;
}
tasks.add(new PollingTask(device.getSiteId(), deviceConfig));
}
return tasks;
}
private DeviceConfig buildDeviceConfig(EmsDevicesSetting device, List<EmsPointConfig> pointConfigs) {
if (device == null || CollectionUtils.isEmpty(pointConfigs)) {
return null;
}
if (StringUtils.isBlank(device.getIpAddress()) || device.getIpPort() == null || device.getSlaveId() == null) {
log.warn("设备连接参数不完整,跳过采集: siteId={}, deviceId={}", device.getSiteId(), device.getDeviceId());
return null;
}
List<TagConfig> tags = pointConfigs.stream()
.sorted(Comparator.comparing(point -> point.getModbusReadOrder() == null ? 0 : point.getModbusReadOrder()))
.map(this::toTagConfig)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(tags)) {
log.warn("设备无有效Modbus点位配置跳过采集: siteId={}, deviceId={}", device.getSiteId(), device.getDeviceId());
return null;
}
DeviceConfig deviceConfig = new DeviceConfig();
deviceConfig.setEnabled(true);
deviceConfig.setDeviceName(device.getDeviceName());
deviceConfig.setDeviceNumber(device.getDeviceId());
deviceConfig.setHost(device.getIpAddress());
deviceConfig.setPort(device.getIpPort().intValue());
deviceConfig.setSlaveId(device.getSlaveId().intValue());
deviceConfig.setTags(tags);
return deviceConfig;
}
private TagConfig toTagConfig(EmsPointConfig pointConfig) {
if (pointConfig == null) {
return null;
}
if (StringUtils.isBlank(pointConfig.getDataKey())) {
return null;
}
String address = normalizeAddress(pointConfig.getRegisterAddress(), pointConfig.getModbusRegisterType());
if (StringUtils.isBlank(address)) {
return null;
}
if (StringUtils.isBlank(pointConfig.getModbusDataType())) {
return null;
}
TagConfig tag = new TagConfig();
tag.setKey(pointConfig.getDataKey().trim());
tag.setAddress(address);
tag.setDataType(pointConfig.getModbusDataType().trim());
tag.setA(pointConfig.getDataA() == null ? 0F : pointConfig.getDataA().floatValue());
tag.setK(pointConfig.getDataK() == null ? 1F : pointConfig.getDataK().floatValue());
tag.setB(pointConfig.getDataB() == null ? 0F : pointConfig.getDataB().floatValue());
tag.setBit(pointConfig.getDataBit());
return tag;
}
private String normalizeAddress(String registerAddress, String registerType) {
if (StringUtils.isBlank(registerAddress)) {
return null;
}
String normalizedAddress = registerAddress.trim();
if (!normalizedAddress.chars().allMatch(Character::isDigit)) {
log.warn("寄存器地址必须为数字,当前值: {}", normalizedAddress);
return null;
}
if (normalizedAddress.length() > 1) {
char first = normalizedAddress.charAt(0);
if (first >= '0' && first <= '4') {
return normalizedAddress;
}
}
return getRegisterPrefix(registerType) + normalizedAddress;
}
private String getRegisterPrefix(String registerType) {
String normalized = StringUtils.defaultString(registerType).trim().toUpperCase();
switch (normalized) {
case "COIL":
return "0";
case "DISCRETE_INPUT":
return "1";
case "INPUT_REGISTER":
return "3";
case "HOLDING_REGISTER":
default:
return "4";
}
}
private String buildSiteDeviceKey(EmsDevicesSetting device) {
return device.getSiteId() + "_" + device.getDeviceId();
}
public void scheduledStart(String siteId, DeviceConfig config) {
if (config.isEnabled()) {
log.info("Reading data from devices: {}", config.getDeviceName());
// 带重试的读取最多重试2次
Map<String, Object> data = readWithRetry(config, 2);
List<String> rawValuEmptyList = new ArrayList<>();
// 在这里处理采集到的数据空
config.getTags().forEach(tag -> {
Object rawValue = data.get(tag.getKey());
if (rawValue != null) {
float value = 0;
if (rawValue instanceof Number) {
value = ((Number) rawValue).floatValue(); // 安全地转换为 float
} else {
log.error("tag:{},无法将数据转换为数字: {}", tag.getKey(), rawValue);
}
value = tag.getA() * value * value + tag.getK() * value + tag.getB();
int intValue = (int) value;
if (tag.getBit() != null) {
log.info("tag:{},bit:{},value:{}", tag.getKey(), tag.getBit(), value);
String binary = Integer.toBinaryString(intValue);
data.put(tag.getKey(), binary);
} else {
data.put(tag.getKey(), value);
}
} else {
// data.put(tag.getKey(), rawValue);
// log.warn("tag:{},数据为空: {}", tag.getKey(), rawValue);
rawValuEmptyList.add("tag: " + tag.getKey() + ",数据为空: " + rawValue);
}
});
if (!rawValuEmptyList.isEmpty()) {
log.warn("设备 {} 数据为空: {}", config.getDeviceName(), JSON.toJSONString(rawValuEmptyList));
}
log.info("Data from {}: {}", config.getDeviceName(), data);
String deviceNumber = config.getDeviceNumber();
//处理数据并发送MQTT消息、保存Redis数据和数据入库
processingData(siteId, data, deviceNumber);
}
}
/**
* 带重试的读取方法
*/
private Map<String, Object> readWithRetry(DeviceConfig config, int maxRetries) {
Map<String, Object> data = new HashMap<>();
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
ModbusMaster master = modbusProcessor.borrowMaster(config);
data = modbusProcessor.readDataFromDevice(config, master);
// 如果读取成功(有数据),直接返回
if (!data.isEmpty()) {
if (attempt > 0) {
log.info("设备 {} 第 {} 次重试成功", config.getDeviceName(), attempt);
}
return data;
}
// 读取返回空数据,等待后重试
if (attempt < maxRetries) {
log.warn("设备 {} 读取返回空数据等待1秒后重试 ({}/{})",
config.getDeviceName(), attempt + 1, maxRetries);
Thread.sleep(1000);
}
} catch (Exception e) {
log.error("设备 {} 读取异常 ({}/{}): {}",
config.getDeviceName(), attempt + 1, maxRetries, e.getMessage());
if (attempt < maxRetries) {
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
// 所有重试都失败
log.error("设备 {} 读取失败,已重试 {} 次", config.getDeviceName(), maxRetries);
return data;
}
private void processingData(String siteId, Map<String, Object> data, String deviceNumber) {
String siteDeviceKey = siteId + "_" + deviceNumber;
if (CollectionUtils.isEmpty(data)) {
// 增加失败计数
int failureCount = deviceFailureCounts.getOrDefault(siteDeviceKey, 0) + 1;
deviceFailureCounts.put(siteDeviceKey, failureCount);
log.warn("设备 {} 数据读取失败,当前连续失败次数: {}", siteDeviceKey, failureCount);
// 连续6次失败触发报警
if (failureCount >= SITE_DEVICE_OFFLINE_THRESHOLD) {
addDeviceOfflineRecord(siteId, deviceNumber);
log.error("设备 {} 连续 {} 次未读取到数据,触发报警", siteDeviceKey, failureCount);
}
return; return;
} }
// 数据读取成功,重置计数器
deviceFailureCounts.remove(siteDeviceKey);
// 读取到数据后告警自恢复
deleteDeviceOfflineRecord(siteId, deviceNumber);
// 发送MQTT消息、保存Redis数据和数据入库
Long timestamp = System.currentTimeMillis(); Long timestamp = System.currentTimeMillis();
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
json.put("Data", data); json.put("Data", data);
json.put("timestamp", timestamp); json.put("timestamp", timestamp);
json.put("Device", deviceNumber); json.put("Device", deviceNumber);
sendMqttMsg(json); if (shouldSendMqttOnChange(siteId, deviceNumber, data)) {
saveRedisData(json, deviceNumber); sendMqttMsg(json);
saveDataToDatabase(data, deviceNumber, timestamp); } else {
sendMqttHeartbeat(deviceNumber, timestamp);
log.info("设备 {} 数据无变化已发送心跳MQTT", siteDeviceKey);
}
saveRedisData(siteId, json, deviceNumber);
saveDataToDatabase(siteId, data, deviceNumber, timestamp);
}
/**
* 逢变上送仅当Data发生变化时才发送MQTT
*/
private boolean shouldSendMqttOnChange(String siteId, String deviceNumber, Map<String, Object> currentData) {
JSONObject lastPayload = redisCache.getCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceNumber);
if (lastPayload == null) {
return true;
}
Object lastDataObj = lastPayload.get("Data");
if (lastDataObj == null) {
return true;
}
JSONObject lastData = JSON.parseObject(JSON.toJSONString(lastDataObj));
JSONObject currentDataJson = JSON.parseObject(JSON.toJSONString(currentData));
return !Objects.equals(lastData, currentDataJson);
}
private void sendMqttHeartbeat(String deviceNumber, Long timestamp) {
JSONObject heartbeat = new JSONObject();
heartbeat.put("Device", deviceNumber);
heartbeat.put("timestamp", timestamp);
heartbeat.put("Heartbeat", 1);
heartbeat.put("Data", new JSONObject());
sendMqttMsg(heartbeat);
} }
public void sendMqttMsg(JSONObject json) { public void sendMqttMsg(JSONObject json) {
try { try {
mqttPublisher.publish(topic, Collections.singletonList(json).toString(), 0); mqttPublisher.publish(topic, Collections.singletonList(json).toString(), 0);
} catch (MqttException e) { } catch (MqttException e) {
log.error("MQTT消息发布失败: {}", json.toJSONString(), e); log.error("MQTT消息发布失败: {}, reason code: {}", json.toJSONString(), e.getReasonCode() ,e);
} }
log.info("已发送数据: {}", json.toJSONString()); log.info("已发送数据: {}", json.toJSONString());
} }
public void saveRedisData(JSONObject obj, String deviceNumber) { public void saveRedisData(String siteId, JSONObject obj, String deviceNumber) {
try { try {
// 存放mqtt原始每个设备最晚一次数据便于后面点位获取数据 // 存放mqtt原始每个设备最晚一次数据便于后面点位获取数据
redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceNumber, obj); redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceNumber, obj);
// 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据和保护策略查询 // 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据和保护策略查询
redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA_ALARM + siteId + "_" + deviceNumber, obj, 1, TimeUnit.MINUTES); redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceNumber, obj, 1, TimeUnit.MINUTES);
log.info("数据已成功存储在Redis: {}", deviceNumber); log.info("数据已成功存储在Redis: {}", deviceNumber);
} catch (Exception e) { } catch (Exception e) {
log.error("无法在设备的Redis中存储数据: {}", deviceNumber, e); log.error("无法在设备的Redis中存储数据: {}", deviceNumber, e);
} }
} }
private void saveDataToDatabase(Map<String, Object> data, String deviceNumber, Long timestamp) { private void saveDataToDatabase(String siteId, Map<String, Object> data, String deviceNumber, Long timestamp) {
deviceDataProcessServiceImpl.processingDeviceData(siteId, deviceNumber, JSON.toJSONString(data), DateUtils.convertUpdateTime(timestamp)); JSONObject payload = new JSONObject();
payload.put("Device", deviceNumber);
payload.put("Data", JSON.toJSONString(data));
payload.put("timestamp", timestamp);
deviceDataProcessServiceImpl.handleDeviceData(Collections.singletonList(payload).toString(), siteId);
} }
} //处理设备连接失败的情况,更新设备状态为离线,添加报警记录
private void addDeviceOfflineRecord(String siteId, String deviceNumber) {
updateDeviceStatus(siteId, deviceNumber, DeviceRunningStatus.OFFLINE.getCode());
iEmsAlarmRecordsService.addDeviceOfflineRecord(siteId, deviceNumber);
}
//处理设备读取到数据的情况,更新设备状态为在线,报警记录自恢复
private void deleteDeviceOfflineRecord(String siteId, String deviceNumber) {
updateDeviceStatus(siteId, deviceNumber, DeviceRunningStatus.ONLINE.getCode());
iEmsAlarmRecordsService.deleteDeviceOfflineRecord(siteId, deviceNumber);
}
// 更新设备状态为在线或离线
private void updateDeviceStatus(String siteId, String deviceNumber, String deviceStatus) {
EmsDevicesSetting emsDevicesSetting = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceNumber, siteId);
if (emsDevicesSetting != null && !Objects.equals(emsDevicesSetting.getDeviceStatus(), deviceStatus)) {
emsDevicesSetting.setDeviceStatus(deviceStatus);
emsDevicesSettingMapper.updateEmsDevicesSetting(emsDevicesSetting);
}
}
private static class PollingTask {
private final String siteId;
private final DeviceConfig deviceConfig;
private PollingTask(String siteId, DeviceConfig deviceConfig) {
this.siteId = siteId;
this.deviceConfig = deviceConfig;
}
public String getSiteId() {
return siteId;
}
public DeviceConfig getDeviceConfig() {
return deviceConfig;
}
}
}

View File

@ -10,16 +10,17 @@ import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.AlarmLevelStatus; import com.xzzn.common.enums.AlarmLevelStatus;
import com.xzzn.common.enums.AlarmStatus; import com.xzzn.common.enums.AlarmStatus;
import com.xzzn.common.enums.ProtPlanStatus; import com.xzzn.common.enums.ProtPlanStatus;
import com.xzzn.common.enums.StrategyStatus;
import com.xzzn.common.utils.StringUtils; import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.*; import com.xzzn.ems.domain.EmsAlarmRecords;
import com.xzzn.ems.domain.EmsFaultProtectionPlan;
import com.xzzn.ems.domain.vo.ProtectionConstraintVo;
import com.xzzn.ems.domain.vo.ProtectionPlanVo; import com.xzzn.ems.domain.vo.ProtectionPlanVo;
import com.xzzn.ems.domain.vo.ProtectionSettingVo; import com.xzzn.ems.domain.vo.ProtectionSettingVo;
import com.xzzn.ems.mapper.*; import com.xzzn.ems.domain.vo.ProtectionSettingsGroupVo;
import com.xzzn.ems.mapper.EmsAlarmRecordsMapper;
import com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper;
import com.xzzn.ems.service.IEmsFaultProtectionPlanService; import com.xzzn.ems.service.IEmsFaultProtectionPlanService;
import com.xzzn.framework.manager.ModbusConnectionManager; import org.apache.commons.collections4.CollectionUtils;
import com.xzzn.framework.manager.ModbusConnectionWrapper;
import com.xzzn.framework.web.service.ModbusService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -28,21 +29,27 @@ import org.springframework.util.ObjectUtils;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.*; import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* 告警保护方案轮询 * 告警保护方案轮询
*
* @author xzzn
*/ */
@Component("protectionPlanTask") @Component("protectionPlanTask")
public class ProtectionPlanTask { public class ProtectionPlanTask {
private static final Logger logger = LoggerFactory.getLogger(ProtectionPlanTask.class); private static final Logger logger = LoggerFactory.getLogger(ProtectionPlanTask.class);
private static final BigDecimal DEFAULT_L1_POWER_RATIO = new BigDecimal("0.5");
private static final int CONSTRAINT_TTL_SECONDS = 120;
@Resource(name = "scheduledExecutorService") @Resource(name = "scheduledExecutorService")
private ScheduledExecutorService scheduledExecutorService; private ScheduledExecutorService scheduledExecutorService;
@Autowired @Autowired
@ -50,20 +57,11 @@ public class ProtectionPlanTask {
@Autowired @Autowired
private EmsAlarmRecordsMapper emsAlarmRecordsMapper; private EmsAlarmRecordsMapper emsAlarmRecordsMapper;
@Autowired @Autowired
private EmsStrategyRunningMapper emsStrategyRunningMapper;
@Autowired
private EmsFaultProtectionPlanMapper emsFaultProtectionPlanMapper; private EmsFaultProtectionPlanMapper emsFaultProtectionPlanMapper;
@Autowired @Autowired
private RedisCache redisCache; private RedisCache redisCache;
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private EmsDevicesSettingMapper emsDevicesSettingMapper;
@Autowired
private ModbusConnectionManager connectionManager;
@Autowired
private ModbusService modbusService;
@Autowired
private EmsFaultIssueLogMapper emsFaultIssueLogMapper;
public ProtectionPlanTask(IEmsFaultProtectionPlanService iEmsFaultProtectionPlanService) { public ProtectionPlanTask(IEmsFaultProtectionPlanService iEmsFaultProtectionPlanService) {
this.iEmsFaultProtectionPlanService = iEmsFaultProtectionPlanService; this.iEmsFaultProtectionPlanService = iEmsFaultProtectionPlanService;
@ -72,273 +70,456 @@ public class ProtectionPlanTask {
public void pollPlanList() { public void pollPlanList() {
Long planId = 0L; Long planId = 0L;
try { try {
// 获取所有方案,轮询
List<EmsFaultProtectionPlan> planList = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(null); List<EmsFaultProtectionPlan> planList = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(null);
for (EmsFaultProtectionPlan plan : planList) { for (EmsFaultProtectionPlan plan : planList) {
planId = plan.getId(); planId = plan.getId();
String siteId = plan.getSiteId(); String siteId = plan.getSiteId();
if (StringUtils.isEmpty(siteId)) { if (StringUtils.isEmpty(siteId)) {
return; continue;
} }
// 保护前提 ProtectionSettingsGroupVo settingGroup = parseProtectionSettings(plan.getProtectionSettings());
String protectionSettings = plan.getProtectionSettings(); if (CollectionUtils.isEmpty(settingGroup.getFaultSettings())
final List<ProtectionSettingVo> protSettings = objectMapper.readValue( && CollectionUtils.isEmpty(settingGroup.getReleaseSettings())) {
protectionSettings, continue;
new TypeReference<List<ProtectionSettingVo>>() {}
);
if (protSettings == null) {
return;
}
// 处理告警保护方案
boolean isHighLevel = dealWithProtectionPlan(plan, protSettings);
if (isHighLevel) {
// 触发最高故障等级-结束循环
return;
} }
dealWithProtectionPlan(plan, settingGroup);
} }
refreshProtectionConstraintCache(planList);
} catch (Exception e) { } catch (Exception e) {
logger.error("轮询失败,方案id为{}", planId, e); logger.error("轮询失败,方案id为{}", planId, e);
} }
} }
// 处理告警保护方案-返回触发下发方案时是否最高等级
// 需要同步云端
@SyncAfterInsert @SyncAfterInsert
private boolean dealWithProtectionPlan(EmsFaultProtectionPlan plan, List<ProtectionSettingVo> protSettings) { private void dealWithProtectionPlan(EmsFaultProtectionPlan plan, ProtectionSettingsGroupVo settingGroup) {
boolean isHighLevel = false; logger.info("<轮询保护方案> 站点:{}方案ID:{}", plan.getSiteId(), plan.getId());
String siteId = plan.getSiteId(); String siteId = plan.getSiteId();
final Integer isAlertAlarm = plan.getIsAlert(); Integer isAlertAlarm = plan.getIsAlert();
final Long status = plan.getStatus(); List<ProtectionSettingVo> faultSettings = settingGroup.getFaultSettings();
// 看方案是否启用,走不同判断 List<ProtectionSettingVo> releaseSettings = settingGroup.getReleaseSettings();
if (status == ProtPlanStatus.STOP.getCode()) { Long status = plan.getStatus();
// 未启用,获取方案的故障值与最新数据判断是否需要下发方案 if (status == null) {
if(checkIsNeedIssuedPlan(protSettings, siteId)){ status = ProtPlanStatus.STOP.getCode();
if("3".equals(plan.getFaultLevel())){ }
isHighLevel = true;//最高故障等级
}
// 延时
final int faultDelay = plan.getFaultDelaySeconds().intValue();
ScheduledFuture<?> delayTask = scheduledExecutorService.schedule(() -> {
// 延时后再次确认是否仍满足触发条件(防止期间状态变化)
if (checkIsNeedIssuedPlan(protSettings, siteId)) {
// 判断是否需要生成告警
if (isAlertAlarm == 1) {
logger.info("<生成告警> 方案ID:{},站点:{}", plan.getId(), siteId);
EmsAlarmRecords alarmRecords = addAlarmRecord(siteId,plan.getFaultName(),
getAlarmLevel(plan.getFaultLevel()));
emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords);
}
// 是否有保护方案有则通过modbus连接设备下发方案 if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) {
String protPlanJson = plan.getProtectionPlan(); if (checkIsNeedIssuedPlan(faultSettings, siteId)) {
if (protPlanJson != null && !protPlanJson.isEmpty()) { int faultDelay = safeDelaySeconds(plan.getFaultDelaySeconds(), 0);
logger.info("<下发保护方案> 方案内容:{}", protPlanJson); scheduledExecutorService.schedule(() -> {
executeProtectionActions(protPlanJson,siteId,plan.getId(),plan.getFaultLevel()); // 执行Modbus指令 if (!checkIsNeedIssuedPlan(faultSettings, siteId)) {
} return;
// 更新方案状态为“已启用”
logger.info("<方案已启用> 方案ID:{}", plan.getId());
plan.setStatus(ProtPlanStatus.RUNNING.getCode());
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
} }
}, faultDelay, TimeUnit.SECONDS); if (Integer.valueOf(1).equals(isAlertAlarm)) {
} EmsAlarmRecords alarmRecords = addAlarmRecord(siteId, plan.getFaultName(), getAlarmLevel(plan.getFaultLevel()));
} else { emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords);
// 已启用,则获取方案的释放值与最新数据判断是否需要取消方案
if(checkIsNeedCancelPlan(protSettings, siteId)){
// 延时,
int releaseDelay = plan.getReleaseDelaySeconds().intValue();
ScheduledFuture<?> delayTask = scheduledExecutorService.schedule(() -> {
// 判断是否已存在未处理告警,有着取消
if(isAlertAlarm == 1){
logger.info("<取消告警>");
EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord(siteId,
plan.getFaultName(),getAlarmLevel(plan.getFaultLevel()));
if(emsAlarmRecords != null){
emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode());
emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords);
}
} }
// 更新方案状态为“未启用” plan.setStatus(ProtPlanStatus.RUNNING.getCode());
logger.info("<方案变更为未启用> 方案ID:{}", plan.getId());
plan.setStatus(ProtPlanStatus.STOP.getCode());
plan.setUpdateBy("system"); plan.setUpdateBy("system");
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan); emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
// 更新该站点策略为启用 refreshSiteProtectionConstraint(siteId);
updateStrategyRunning(siteId); }, faultDelay, TimeUnit.SECONDS);
}, releaseDelay, TimeUnit.SECONDS);
} }
return;
} }
return isHighLevel; if (checkIsNeedCancelPlan(releaseSettings, siteId)) {
} int releaseDelay = safeDelaySeconds(plan.getReleaseDelaySeconds(), 0);
scheduledExecutorService.schedule(() -> {
// 下发保护方案 if (Integer.valueOf(1).equals(isAlertAlarm)) {
private void executeProtectionActions(String protPlanJson, String siteId, Long planId, Integer faultLevel){ EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord(
final List<ProtectionPlanVo> protPlanList; siteId,
try { plan.getFaultName(),
protPlanList = objectMapper.readValue( getAlarmLevel(plan.getFaultLevel())
protPlanJson, );
new TypeReference<List<ProtectionPlanVo>>() {} if (emsAlarmRecords != null) {
); emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode());
if (protPlanList == null) { emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords);
return; }
}
// 遍历保护方案
for (ProtectionPlanVo plan : protPlanList) {
if (StringUtils.isEmpty(plan.getDeviceId()) || StringUtils.isEmpty(plan.getPoint())) {
return;
} }
// 给设备发送指令记录日志,并同步云端 plan.setStatus(ProtPlanStatus.STOP.getCode());
EmsFaultIssueLog faultIssueLog = createLogEntity(plan,siteId); plan.setUpdateBy("system");
faultIssueLog.setLogLevel(faultLevel); emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
emsFaultIssueLogMapper.insertEmsFaultIssueLog(faultIssueLog); refreshSiteProtectionConstraint(siteId);
}, releaseDelay, TimeUnit.SECONDS);
}
}
// 通过modbus连接设备发送数据 private int safeDelaySeconds(Long delay, int defaultSeconds) {
executeSinglePlan(plan,siteId); if (delay == null || delay < 0) {
return defaultSeconds;
}
return delay.intValue();
}
private ProtectionSettingsGroupVo parseProtectionSettings(String settingsJson) {
if (StringUtils.isEmpty(settingsJson)) {
return ProtectionSettingsGroupVo.empty();
}
try {
if (settingsJson.trim().startsWith("[")) {
List<ProtectionSettingVo> legacy = objectMapper.readValue(
settingsJson,
new TypeReference<List<ProtectionSettingVo>>() {}
);
ProtectionSettingsGroupVo groupVo = ProtectionSettingsGroupVo.empty();
groupVo.setFaultSettings(legacy);
groupVo.setReleaseSettings(legacy);
return groupVo;
}
ProtectionSettingsGroupVo groupVo = objectMapper.readValue(settingsJson, ProtectionSettingsGroupVo.class);
if (groupVo == null) {
return ProtectionSettingsGroupVo.empty();
}
if (groupVo.getFaultSettings() == null) {
groupVo.setFaultSettings(new ArrayList<>());
}
if (groupVo.getReleaseSettings() == null) {
groupVo.setReleaseSettings(new ArrayList<>());
}
return groupVo;
} catch (Exception e) {
logger.error("解析保护前提失败json:{}", settingsJson, e);
return ProtectionSettingsGroupVo.empty();
}
}
private List<ProtectionPlanVo> parseProtectionPlan(String planJson) {
if (StringUtils.isEmpty(planJson)) {
return new ArrayList<>();
}
try {
if (planJson.trim().startsWith("[")) {
List<ProtectionPlanVo> plans = objectMapper.readValue(
planJson,
new TypeReference<List<ProtectionPlanVo>>() {}
);
return plans == null ? new ArrayList<>() : plans;
}
ProtectionPlanVo plan = objectMapper.readValue(planJson, ProtectionPlanVo.class);
List<ProtectionPlanVo> plans = new ArrayList<>();
if (plan != null) {
plans.add(plan);
}
return plans;
} catch (Exception e) {
logger.error("解析执行保护失败json:{}", planJson, e);
return new ArrayList<>();
}
}
private void refreshProtectionConstraintCache(List<EmsFaultProtectionPlan> allPlans) {
Map<String, List<EmsFaultProtectionPlan>> planBySite = new HashMap<>();
for (EmsFaultProtectionPlan plan : allPlans) {
if (StringUtils.isEmpty(plan.getSiteId())) {
continue;
}
planBySite.computeIfAbsent(plan.getSiteId(), k -> new ArrayList<>()).add(plan);
}
for (Map.Entry<String, List<EmsFaultProtectionPlan>> entry : planBySite.entrySet()) {
writeSiteProtectionConstraint(entry.getKey(), entry.getValue());
}
}
private void refreshSiteProtectionConstraint(String siteId) {
if (StringUtils.isEmpty(siteId)) {
return;
}
EmsFaultProtectionPlan query = new EmsFaultProtectionPlan();
query.setSiteId(siteId);
List<EmsFaultProtectionPlan> sitePlans = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(query);
writeSiteProtectionConstraint(siteId, sitePlans);
}
private void writeSiteProtectionConstraint(String siteId, List<EmsFaultProtectionPlan> sitePlans) {
List<EmsFaultProtectionPlan> runningPlans = new ArrayList<>();
for (EmsFaultProtectionPlan plan : sitePlans) {
if (Objects.equals(plan.getStatus(), ProtPlanStatus.RUNNING.getCode())) {
runningPlans.add(plan);
}
}
String key = RedisKeyConstants.PROTECTION_CONSTRAINT + siteId;
if (runningPlans.isEmpty()) {
redisCache.deleteObject(key);
return;
}
ProtectionConstraintVo merged = ProtectionConstraintVo.empty();
for (EmsFaultProtectionPlan runningPlan : runningPlans) {
ProtectionConstraintVo single = buildConstraintFromPlan(runningPlan);
mergeConstraint(merged, single);
}
merged.setUpdateAt(System.currentTimeMillis());
redisCache.setCacheObject(key, merged, CONSTRAINT_TTL_SECONDS, TimeUnit.SECONDS);
}
private ProtectionConstraintVo buildConstraintFromPlan(EmsFaultProtectionPlan plan) {
ProtectionConstraintVo vo = ProtectionConstraintVo.empty();
int level = plan.getFaultLevel() == null ? 0 : plan.getFaultLevel();
vo.setLevel(level);
vo.setSourcePlanIds(new ArrayList<>());
vo.getSourcePlanIds().add(plan.getId());
if (level == 1) {
vo.setPowerLimitRatio(DEFAULT_L1_POWER_RATIO);
} else if (level >= 3) {
vo.setForceStop(true);
vo.setForceStandby(true);
vo.setAllowCharge(false);
vo.setAllowDischarge(false);
vo.setPowerLimitRatio(BigDecimal.ZERO);
}
String description = StringUtils.isEmpty(plan.getDescription()) ? "" : plan.getDescription();
BigDecimal ratioByDesc = parseDerateRatio(description);
if (ratioByDesc != null) {
vo.setPowerLimitRatio(minRatio(vo.getPowerLimitRatio(), ratioByDesc));
}
if (description.contains("禁止充放电")) {
vo.setAllowCharge(false);
vo.setAllowDischarge(false);
} else {
if (description.contains("禁止充电")) {
vo.setAllowCharge(false);
}
if (description.contains("禁止放电")) {
vo.setAllowDischarge(false);
}
if (description.contains("允许充电")) {
vo.setAllowCharge(true);
}
if (description.contains("允许放电")) {
vo.setAllowDischarge(true);
}
}
if (description.contains("待机")) {
vo.setForceStandby(true);
}
if (description.contains("停机") || description.contains("切断") || level >= 3) {
vo.setForceStop(true);
vo.setForceStandby(true);
vo.setAllowCharge(false);
vo.setAllowDischarge(false);
vo.setPowerLimitRatio(BigDecimal.ZERO);
}
// 执行保护配置优先于描述文本配置
List<ProtectionPlanVo> protectionPlans = parseProtectionPlan(plan.getProtectionPlan());
applyCapabilityByProtectionPlan(vo, protectionPlans);
return vo;
}
private void applyCapabilityByProtectionPlan(ProtectionConstraintVo vo, List<ProtectionPlanVo> protectionPlans) {
if (CollectionUtils.isEmpty(protectionPlans)) {
return;
}
for (ProtectionPlanVo item : protectionPlans) {
if (item == null) {
continue;
}
String marker = ((item.getPointName() == null ? "" : item.getPointName()) + " "
+ (item.getPoint() == null ? "" : item.getPoint())).toLowerCase();
if (StringUtils.isEmpty(marker)) {
continue;
} }
if (containsAny(marker, "降功率", "derate", "power_limit", "powerlimit")) {
BigDecimal ratio = parseDerateRatioByPlan(item);
if (ratio != null) {
vo.setPowerLimitRatio(minRatio(vo.getPowerLimitRatio(), ratio));
}
}
} catch (Exception e) { if (!isCapabilityEnabled(item.getValue())) {
logger.error("下发保护方案失败,方案id为", planId, e); continue;
}
if (containsAny(marker, "禁止充放电", "forbid_charge_discharge", "disable_charge_discharge")) {
vo.setAllowCharge(false);
vo.setAllowDischarge(false);
continue;
}
if (containsAny(marker, "禁止充电", "forbid_charge", "disable_charge")) {
vo.setAllowCharge(false);
}
if (containsAny(marker, "禁止放电", "forbid_discharge", "disable_discharge")) {
vo.setAllowDischarge(false);
}
if (containsAny(marker, "允许充电", "allow_charge")) {
vo.setAllowCharge(true);
}
if (containsAny(marker, "允许放电", "allow_discharge")) {
vo.setAllowDischarge(true);
}
if (containsAny(marker, "待机", "standby")) {
vo.setForceStandby(true);
}
if (containsAny(marker, "关机", "停机", "切断", "shutdown", "stop")) {
vo.setForceStop(true);
vo.setForceStandby(true);
vo.setAllowCharge(false);
vo.setAllowDischarge(false);
vo.setPowerLimitRatio(BigDecimal.ZERO);
}
} }
} }
private EmsFaultIssueLog createLogEntity(ProtectionPlanVo plan,String siteId) { private BigDecimal parseDerateRatioByPlan(ProtectionPlanVo planVo) {
EmsFaultIssueLog faultIssueLog = new EmsFaultIssueLog(); BigDecimal value = planVo.getValue();
faultIssueLog.setLogId(UUID.randomUUID().toString()); if (value == null || value.compareTo(BigDecimal.ZERO) < 0) {
faultIssueLog.setLogTime(new Date()); return null;
faultIssueLog.setSiteId(siteId); }
faultIssueLog.setDeviceId(plan.getDeviceId()); if (value.compareTo(BigDecimal.ONE) <= 0) {
faultIssueLog.setPoint(plan.getPoint()); return value;
faultIssueLog.setValue(plan.getValue()); }
faultIssueLog.setCreateBy("sys"); if (value.compareTo(new BigDecimal("100")) <= 0) {
faultIssueLog.setCreateTime(new Date()); return value.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
return faultIssueLog; }
return null;
} }
private void executeSinglePlan(ProtectionPlanVo plan, String siteId) throws Exception { private boolean isCapabilityEnabled(BigDecimal value) {
String deviceId = plan.getDeviceId(); return value == null || value.compareTo(BigDecimal.ZERO) != 0;
// 获取设备地址信息 }
EmsDevicesSetting device = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, siteId);
if (device == null || StringUtils.isEmpty(device.getIpAddress()) || device.getIpPort()==null) { private boolean containsAny(String text, String... markers) {
if (StringUtils.isEmpty(text) || markers == null) {
return false;
}
for (String marker : markers) {
if (!StringUtils.isEmpty(marker) && text.contains(marker)) {
return true;
}
}
return false;
}
private BigDecimal parseDerateRatio(String text) {
if (StringUtils.isEmpty(text)) {
return null;
}
Matcher m = Pattern.compile("降功率\\s*(\\d+(?:\\.\\d+)?)%")
.matcher(text);
if (!m.find()) {
return null;
}
BigDecimal percent = new BigDecimal(m.group(1));
if (percent.compareTo(BigDecimal.ZERO) < 0) {
return null;
}
return percent.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
}
private void mergeConstraint(ProtectionConstraintVo merged, ProtectionConstraintVo incoming) {
if (incoming == null) {
return; return;
} }
// 获取设备连接 merged.setLevel(Math.max(nullSafeInt(merged.getLevel()), nullSafeInt(incoming.getLevel())));
ModbusConnectionWrapper wrapper = connectionManager.getConnection(device); merged.setAllowCharge(boolAnd(merged.getAllowCharge(), incoming.getAllowCharge()));
if (wrapper == null || !wrapper.isActive()) { merged.setAllowDischarge(boolAnd(merged.getAllowDischarge(), incoming.getAllowDischarge()));
logger.info("<设备连接无效>"); merged.setForceStandby(boolOr(merged.getForceStandby(), incoming.getForceStandby()));
return; merged.setForceStop(boolOr(merged.getForceStop(), incoming.getForceStop()));
merged.setPowerLimitRatio(minRatio(merged.getPowerLimitRatio(), incoming.getPowerLimitRatio()));
if (incoming.getSourcePlanIds() != null && !incoming.getSourcePlanIds().isEmpty()) {
if (merged.getSourcePlanIds() == null) {
merged.setSourcePlanIds(new ArrayList<>());
}
merged.getSourcePlanIds().addAll(incoming.getSourcePlanIds());
} }
// 写入寄存器
boolean success = modbusService.writeSingleRegister(
wrapper.getConnection(),
1,
plan.getValue().intValue());
if (!success) {
logger.error("写入失败,设备地址:{}", device.getIpAddress());
}
} }
// 校验释放值是否取消方案 private BigDecimal minRatio(BigDecimal a, BigDecimal b) {
BigDecimal left = a == null ? BigDecimal.ONE : a;
BigDecimal right = b == null ? BigDecimal.ONE : b;
return left.min(right);
}
private int nullSafeInt(Integer value) {
return value == null ? 0 : value;
}
private Boolean boolAnd(Boolean a, Boolean b) {
boolean left = a == null || a;
boolean right = b == null || b;
return left && right;
}
private Boolean boolOr(Boolean a, Boolean b) {
boolean left = a != null && a;
boolean right = b != null && b;
return left || right;
}
private boolean checkIsNeedCancelPlan(List<ProtectionSettingVo> protSettings, String siteId) { private boolean checkIsNeedCancelPlan(List<ProtectionSettingVo> protSettings, String siteId) {
BigDecimal releaseValue = BigDecimal.ZERO;
StringBuilder conditionSb = new StringBuilder(); StringBuilder conditionSb = new StringBuilder();
for (int i = 0; i < protSettings.size(); i++) { for (int i = 0; i < protSettings.size(); i++) {
ProtectionSettingVo vo = protSettings.get(i); ProtectionSettingVo vo = protSettings.get(i);
String deviceId = vo.getDeviceId(); String deviceId = vo.getDeviceId();
String point = vo.getPoint(); String point = vo.getPoint();
releaseValue = vo.getFaultValue(); BigDecimal releaseValue = vo.getReleaseValue();
if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || releaseValue == null if (StringUtils.isEmpty(deviceId)
|| StringUtils.isEmpty(vo.getReleaseOperator())){ || StringUtils.isEmpty(point)
|| releaseValue == null
|| StringUtils.isEmpty(vo.getReleaseOperator())) {
return false; return false;
} }
// 获取点位最新值
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId); BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
if(lastPointValue == null){ if (lastPointValue == null) {
return false; return false;
} }
// 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系)
conditionSb.append(lastPointValue).append(vo.getReleaseOperator()).append(releaseValue); conditionSb.append(lastPointValue).append(vo.getReleaseOperator()).append(releaseValue);
if (i < protSettings.size() - 1) { if (i < protSettings.size() - 1) {
String relation = vo.getRelationNext(); conditionSb.append(" ").append(vo.getRelationNext()).append(" ");
conditionSb.append(" ").append(relation).append(" ");
} }
// 执行比较语句
return executeWithParser(conditionSb.toString());
} }
return true; return executeWithParser(conditionSb.toString());
} }
// 校验故障值是否需要下发方案
private boolean checkIsNeedIssuedPlan(List<ProtectionSettingVo> protSettings, String siteId) { private boolean checkIsNeedIssuedPlan(List<ProtectionSettingVo> protSettings, String siteId) {
BigDecimal faultValue = BigDecimal.ZERO;
StringBuilder conditionSb = new StringBuilder(); StringBuilder conditionSb = new StringBuilder();
for (int i = 0; i < protSettings.size(); i++) { for (int i = 0; i < protSettings.size(); i++) {
ProtectionSettingVo vo = protSettings.get(i); ProtectionSettingVo vo = protSettings.get(i);
String deviceId = vo.getDeviceId(); String deviceId = vo.getDeviceId();
String point = vo.getPoint(); String point = vo.getPoint();
faultValue = vo.getFaultValue(); BigDecimal faultValue = vo.getFaultValue();
if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || faultValue == null if (StringUtils.isEmpty(deviceId)
|| StringUtils.isEmpty(vo.getFaultOperator())){ || StringUtils.isEmpty(point)
|| faultValue == null
|| StringUtils.isEmpty(vo.getFaultOperator())) {
return false; return false;
} }
// 获取点位最新值
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId); BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
if(lastPointValue == null){ if (lastPointValue == null) {
return false; return false;
} }
// 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系)
conditionSb.append(lastPointValue).append(vo.getFaultOperator()).append(faultValue); conditionSb.append(lastPointValue).append(vo.getFaultOperator()).append(faultValue);
if (i < protSettings.size() - 1) { if (i < protSettings.size() - 1) {
String relation = vo.getRelationNext(); conditionSb.append(" ").append(vo.getRelationNext()).append(" ");
conditionSb.append(" ").append(relation).append(" ");
} }
// 执行比较语句
return executeWithParser(conditionSb.toString());
} }
return true; return executeWithParser(conditionSb.toString());
} }
private BigDecimal getPointLastValue(String deviceId, String point, String siteId) { private BigDecimal getPointLastValue(String deviceId, String point, String siteId) {
JSONObject mqttJson = redisCache.getCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId); JSONObject mqttJson = redisCache.getCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId);
if(mqttJson == null){ if (mqttJson == null) {
return null; return null;
} }
String jsonData = mqttJson.get("Data").toString(); String jsonData = mqttJson.get("Data").toString();
if(StringUtils.isEmpty(jsonData)){ if (StringUtils.isEmpty(jsonData)) {
return null; return null;
} }
Map<String, Object> obj = JSON.parseObject(jsonData, new com.alibaba.fastjson2.TypeReference<Map<String, Object>>() {}); Map<String, Object> obj = JSON.parseObject(jsonData, new com.alibaba.fastjson2.TypeReference<Map<String, Object>>() {});
return StringUtils.getBigDecimal(obj.get(point)); return StringUtils.getBigDecimal(obj.get(point));
} }
// 更新站点策略为启用 private EmsAlarmRecords addAlarmRecord(String siteId, String content, String level) {
private void updateStrategyRunning(String siteId) {
// 获取是否有正在运行的策略,如果有则不更改
EmsStrategyRunning emsStrategyRunning = emsStrategyRunningMapper.getRunningStrategy(siteId);
if (emsStrategyRunning == null) {
// 获取已存在并且状态为:未启用和已暂停的最晚一条策略,更新为已启用
emsStrategyRunning = emsStrategyRunningMapper.getPendingStrategy(siteId);
emsStrategyRunning.setStatus(StrategyStatus.RUNNING.getCode());
emsStrategyRunningMapper.updateEmsStrategyRunning(emsStrategyRunning);
}
}
private EmsAlarmRecords addAlarmRecord(String siteId, String content,String level) {
EmsAlarmRecords emsAlarmRecords = new EmsAlarmRecords(); EmsAlarmRecords emsAlarmRecords = new EmsAlarmRecords();
emsAlarmRecords.setSiteId(siteId); emsAlarmRecords.setSiteId(siteId);
emsAlarmRecords.setAlarmContent(content); emsAlarmRecords.setAlarmContent(content);
@ -351,29 +532,31 @@ public class ProtectionPlanTask {
return emsAlarmRecords; return emsAlarmRecords;
} }
// 故障等级-告警等级匹配
private String getAlarmLevel(Integer faultLevel) { private String getAlarmLevel(Integer faultLevel) {
if (ObjectUtils.isEmpty(faultLevel) || faultLevel < 1 || faultLevel > 3) { if (ObjectUtils.isEmpty(faultLevel) || faultLevel < 1 || faultLevel > 3) {
logger.warn("非法故障等级:{},默认返回普通告警", faultLevel); logger.warn("非法故障等级:{},默认返回紧急告警", faultLevel);
return AlarmLevelStatus.EMERGENCY.getCode(); return AlarmLevelStatus.EMERGENCY.getCode();
} }
switch (faultLevel) { switch (faultLevel) {
case 1: return AlarmLevelStatus.GENERAL.getCode(); case 1:
case 2: return AlarmLevelStatus.SERIOUS.getCode(); return AlarmLevelStatus.GENERAL.getCode();
case 3: return AlarmLevelStatus.EMERGENCY.getCode(); case 2:
return AlarmLevelStatus.SERIOUS.getCode();
case 3:
return AlarmLevelStatus.EMERGENCY.getCode();
default: default:
logger.error("未匹配的故障等级:{}", faultLevel);
return AlarmLevelStatus.EMERGENCY.getCode(); return AlarmLevelStatus.EMERGENCY.getCode();
} }
} }
// 自定义表达式解析器(仅支持简单运算符和逻辑关系) /**
* 自定义表达式解析器(仅支持简单运算符和逻辑关系)
*/
public boolean executeWithParser(String conditionStr) { public boolean executeWithParser(String conditionStr) {
if (conditionStr == null || conditionStr.isEmpty()) { if (conditionStr == null || conditionStr.isEmpty()) {
return false; return false;
} }
// 1. 拆分逻辑关系(提取 && 或 ||
List<String> logicRelations = new ArrayList<>(); List<String> logicRelations = new ArrayList<>();
Pattern logicPattern = Pattern.compile("(&&|\\|\\|)"); Pattern logicPattern = Pattern.compile("(&&|\\|\\|)");
Matcher logicMatcher = logicPattern.matcher(conditionStr); Matcher logicMatcher = logicPattern.matcher(conditionStr);
@ -381,10 +564,7 @@ public class ProtectionPlanTask {
logicRelations.add(logicMatcher.group()); logicRelations.add(logicMatcher.group());
} }
// 2. 拆分原子条件(如 "3.55>3.52"
String[] atomicConditions = logicPattern.split(conditionStr); String[] atomicConditions = logicPattern.split(conditionStr);
// 3. 解析每个原子条件并计算结果
List<Boolean> atomicResults = new ArrayList<>(); List<Boolean> atomicResults = new ArrayList<>();
Pattern conditionPattern = Pattern.compile("(\\d+\\.?\\d*)\\s*([><]=?|==)\\s*(\\d+\\.?\\d*)"); Pattern conditionPattern = Pattern.compile("(\\d+\\.?\\d*)\\s*([><]=?|==)\\s*(\\d+\\.?\\d*)");
for (String atomic : atomicConditions) { for (String atomic : atomicConditions) {
@ -393,11 +573,10 @@ public class ProtectionPlanTask {
logger.error("无效的原子条件:{}", atomic); logger.error("无效的原子条件:{}", atomic);
return false; return false;
} }
double left = Double.parseDouble(matcher.group(1)); // 左值(最新值) double left = Double.parseDouble(matcher.group(1));
String operator = matcher.group(2); // 运算符 String operator = matcher.group(2);
double right = Double.parseDouble(matcher.group(3)); // 右值(故障值) double right = Double.parseDouble(matcher.group(3));
// 执行比较
boolean result; boolean result;
switch (operator) { switch (operator) {
case ">": case ">":
@ -422,7 +601,6 @@ public class ProtectionPlanTask {
atomicResults.add(result); atomicResults.add(result);
} }
// 4. 组合原子结果(根据逻辑关系)
boolean finalResult = atomicResults.get(0); boolean finalResult = atomicResults.get(0);
for (int i = 0; i < logicRelations.size(); i++) { for (int i = 0; i < logicRelations.size(); i++) {
String relation = logicRelations.get(i); String relation = logicRelations.get(i);

View File

@ -1,44 +1,103 @@
package com.xzzn.quartz.task; package com.xzzn.quartz.task;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.xzzn.common.constant.RedisKeyConstants;
import com.xzzn.common.core.modbus.ModbusProcessor;
import com.xzzn.common.core.modbus.domain.DeviceConfig;
import com.xzzn.common.core.modbus.domain.WriteTagConfig;
import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.BusinessStatus;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.common.enums.ChargeStatus;
import com.xzzn.common.enums.DeviceCategory;
import com.xzzn.common.enums.OperatorType;
import com.xzzn.common.enums.SiteDevice;
import com.xzzn.common.enums.SocLimit;
import com.xzzn.common.enums.WorkStatus;
import com.xzzn.common.utils.DateUtils; import com.xzzn.common.utils.DateUtils;
import com.xzzn.common.utils.StringUtils; import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsStrategyCurve; import com.xzzn.ems.domain.EmsAmmeterData;
import com.xzzn.ems.domain.EmsBatteryStack;
import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsPcsSetting;
import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
import com.xzzn.ems.domain.EmsStrategyLog;
import com.xzzn.ems.domain.EmsStrategyTemp; import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.EmsStrategyTimeConfig; import com.xzzn.ems.domain.EmsStrategyTimeConfig;
import com.xzzn.ems.domain.vo.StrategyPowerDataVo; import com.xzzn.ems.domain.vo.ProtectionConstraintVo;
import com.xzzn.ems.domain.vo.StrategyRunningVo; import com.xzzn.ems.domain.vo.StrategyRunningVo;
import com.xzzn.ems.mapper.*; import com.xzzn.ems.mapper.EmsAmmeterDataMapper;
import com.xzzn.framework.manager.ModbusConnectionManager; import com.xzzn.ems.mapper.EmsBatteryStackMapper;
import com.xzzn.framework.manager.MqttLifecycleManager; import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.framework.web.service.ModbusService; import com.xzzn.ems.mapper.EmsPcsSettingMapper;
import com.xzzn.ems.mapper.EmsStrategyLogMapper;
import com.xzzn.ems.mapper.EmsStrategyRuntimeConfigMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.mapper.EmsStrategyTimeConfigMapper;
import com.xzzn.system.domain.SysOperLog;
import com.xzzn.system.service.ISysOperLogService;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import javax.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Component("strategyPoller") @Component("strategyPoller")
public class StrategyPoller { public class StrategyPoller {
private static final Logger logger = LoggerFactory.getLogger(StrategyPoller.class); private static final Logger logger = LoggerFactory.getLogger(StrategyPoller.class);
private final MqttLifecycleManager mqttLifecycleManager; private static final ConcurrentHashMap<Long, Boolean> strategyLocks = new ConcurrentHashMap<>();
// SOC 上下限值默认为0%-100%
private static final BigDecimal DEFAULT_SOC_DOWN = BigDecimal.ZERO;
private static final BigDecimal DEFAULT_SOC_UP = new BigDecimal(100);
// 逆变器功率下限值默认为30kW
private static final BigDecimal DEFAULT_ANTI_REVERSE_THRESHOLD = new BigDecimal(30);
// 逆变器下限值范围默认为20%
private static final BigDecimal DEFAULT_ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20);
// 逆变器功率上限值默认为100kW
private static final BigDecimal DEFAULT_ANTI_REVERSE_UP = new BigDecimal(100);
// PCS功率降幅默认为10%
private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10);
// 电网有功功率低于20kW时强制待机
private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal(20);
// 设定功率倍率默认10
private static final BigDecimal DEFAULT_POWER_SET_MULTIPLIER = new BigDecimal(10);
// 保护介入默认开启
private static final Integer DEFAULT_PROTECT_INTERVENE_ENABLE = 1;
// 一级保护默认降额50%
private static final BigDecimal DEFAULT_PROTECT_L1_DERATE_PERCENT = new BigDecimal("50");
// 保护约束失效保护时长(秒)
private static final Integer DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS = 5;
// 三级保护默认锁存开启
private static final Integer DEFAULT_PROTECT_L3_LATCH_ENABLE = 1;
// 保护冲突策略默认值
private static final String DEFAULT_PROTECT_CONFLICT_POLICY = "MAX_LEVEL_WIN";
// 保护约束默认功率比例
private static final BigDecimal DEFAULT_PROTECTION_RATIO = BigDecimal.ONE;
// 除法精度避免BigDecimal除不尽异常
private static final int POWER_SCALE = 4;
@Autowired
private ModbusConnectionManager connectionManager;
@Autowired
private ModbusService modbusService;
@Autowired
private EmsDevicesSettingMapper deviceRepo;
@Autowired
private EmsMqttMessageMapper emsMqttMessageMapper;
@Autowired @Autowired
private EmsStrategyRunningMapper emsStrategyRunningMapper; private EmsStrategyRunningMapper emsStrategyRunningMapper;
@Autowired @Autowired
@ -46,33 +105,52 @@ public class StrategyPoller {
@Autowired @Autowired
private EmsStrategyTimeConfigMapper emsStrategyTimeConfigMapper; private EmsStrategyTimeConfigMapper emsStrategyTimeConfigMapper;
@Autowired @Autowired
private EmsStrategyCurveMapper emsStrategyCurveMapper; private EmsBatteryStackMapper emsBatteryStackMapper;
@Autowired @Autowired
public StrategyPoller(MqttLifecycleManager mqttLifecycleManager) { private EmsDevicesSettingMapper emsDevicesMapper;
this.mqttLifecycleManager = mqttLifecycleManager; @Autowired
} private EmsPcsSettingMapper emsPcsSettingMapper;
@Autowired
private EmsAmmeterDataMapper emsAmmeterDataMapper;
@Autowired
private EmsStrategyLogMapper emsStrategyLogMapper;
@Autowired
private EmsStrategyRuntimeConfigMapper runtimeConfigMapper;
@Autowired
private RedisCache redisCache;
@Autowired
private ModbusProcessor modbusProcessor;
@Autowired
private ISysOperLogService operLogService;
@Resource(name = "modbusExecutor")
private ExecutorService modbusExecutor;
public void pollAllDevices() { public void pollAllDevices() {
logger.info("开始执行策略数据轮询..."); logger.info("开始执行运行策略数据轮询...");
List<StrategyRunningVo> strategyRunningVoList = emsStrategyRunningMapper.getPendingPollerStrategy(null); List<StrategyRunningVo> strategyRunningVoList = emsStrategyRunningMapper.getPendingPollerStrategy(null);
strategyRunningVoList.forEach(strategyVo -> { strategyRunningVoList.forEach(strategyVo -> {
try { Long strategyId = strategyVo.getId();
CompletableFuture.runAsync(() -> { if (strategyLocks.putIfAbsent(strategyId, true) == null) {
processData(strategyVo); // 使用共享的modbusExecutor串行执行避免与ModbusPoller并发访问导致通讯故障
}) modbusExecutor.submit(() -> {
.exceptionally(e -> { try {
logger.error("策略{}轮询异常", strategyVo.getId(), e); processData(strategyVo);
return null; } catch (Exception e) {
}); logger.error("运行策略{}轮询异常", strategyVo.getId(), e);
} catch (Exception e) { } finally {
logger.error("策略下方{}任务失败", strategyVo.getId(), e); logger.info("运行策略{}轮询任务执行完成,释放锁", strategyVo.getId());
strategyLocks.remove(strategyId);
}
});
} else {
logger.info("策略{}已在处理中,跳过重复执行", strategyId);
} }
}); });
} }
// 处理获取到的数据发到mqtt服务上 // 处理获取到的运行策略数据modbus发送指定的命令控制设备
private void processData(StrategyRunningVo strategyVo) { private void processData(StrategyRunningVo strategyVo) {
logger.info("策略下发数据处理开始"); logger.info("运行策略数据处理开始");
// 根据运行策略获取主副策略的模板数据 // 根据运行策略获取主副策略的模板数据
Long mainStrategyId = strategyVo.getMainStrategyId(); Long mainStrategyId = strategyVo.getMainStrategyId();
Long auxStrategyId = strategyVo.getAuxStrategyId(); Long auxStrategyId = strategyVo.getAuxStrategyId();
@ -86,78 +164,613 @@ public class StrategyPoller {
dealStrategyCurveData(auxStrategyId, siteId); dealStrategyCurveData(auxStrategyId, siteId);
} }
// 策略数据下发-下方格式暂无 logger.info("运行策略轮询处理结束");
logger.info("策略下发结束");
} }
private void dealStrategyCurveData(Long mainStrategyId, String siteId) { private void dealStrategyCurveData(Long strategyId, String siteId) {
// 获取当前策略的所有模板 EmsStrategyRuntimeConfig runtimeConfig = getRuntimeConfig(siteId);
List<Map<String, String>> temps = emsStrategyTempMapper.getTempNameList(mainStrategyId,siteId); // 1.获取当前策略的所有模板
if (temps != null && temps.size() > 0) { List<Map<String, String>> temps = emsStrategyTempMapper.getTempNameList(strategyId, siteId);
for (Map<String, String> temp : temps) { if (CollectionUtils.isEmpty(temps)) {
String tempId = temp.get("templateId"); logger.info("当前站点: {}, 策略: {} 没有模板数据", siteId, strategyId);
List<EmsStrategyTimeConfig> timeConfigs = emsStrategyTimeConfigMapper.getAllTimeConfigByTempId(tempId); return;
if (timeConfigs != null && timeConfigs.size() > 0) { }
for (EmsStrategyTimeConfig timeConfig : timeConfigs) { for (Map<String, String> temp : temps) {
EmsStrategyCurve curve = new EmsStrategyCurve(); // 2.查询当月配置的运行策略
curve.setStrategyId(mainStrategyId); String tempId = temp.get("templateId");
curve.setSiteId(siteId); int month = LocalDateTime.now().getMonthValue();
curve.setTemplateId(tempId); List<EmsStrategyTimeConfig> timeConfigs = emsStrategyTimeConfigMapper.getTimeConfigByTempIdAndMonth(tempId, month);
curve.setCreateBy("system"); if (CollectionUtils.isEmpty(timeConfigs)) {
curve.setCreateTime(DateUtils.getNowDate()); continue;
curve.setUpdateBy("system"); }
curve.setUpdateTime(DateUtils.getNowDate()); logger.info("当前站点: {}, 策略: {}, {}月配置模版:{}", siteId, strategyId, month, tempId);
// 时间设置 // 3.查询当月配置的运行策略时间阶段数据
int month = Integer.parseInt(timeConfig.getMonth().toString()); List<EmsStrategyTemp> powerConfig = emsStrategyTempMapper.selectStrategyTempByTempId(tempId);
String[] dateList= dealWithMonth(month); if (CollectionUtils.isEmpty(powerConfig)) {
curve.setMonth(Long.valueOf(month)); logger.info("当前站点: {}, 策略: {}, 模版:{} 未配置数据", siteId, strategyId, tempId);
curve.setStartDate(DateUtils.dateTime(DateUtils.YYYY_MM_DD,dateList[0])); continue;
curve.setEndDate(DateUtils.dateTime(DateUtils.YYYY_MM_DD,dateList[1])); }
// powerData-存json格式 // 4.遍历时间段数据,判断当前时间是否在时间段内,在时间段内的进行处理
List<EmsStrategyTemp> powerConfig = emsStrategyTempMapper.selectStrategyTempByTempId(tempId); for (EmsStrategyTemp emsStrategyTemp : powerConfig) {
List<StrategyPowerDataVo> powerDataVoList = new ArrayList<>(); if (emsStrategyTemp.getStartTime() == null || emsStrategyTemp.getEndTime() == null) {
for (int i = 0; i < powerConfig.size(); i++) { logger.info("当前站点: {}, 策略: {}, 模版:{} 未配置时间阶段数据", siteId, strategyId, tempId);
EmsStrategyTemp powerTemp = powerConfig.get(i); continue;
StrategyPowerDataVo powerDataVo = new StrategyPowerDataVo(); }
powerDataVo.setPowerData(powerTemp.getChargeDischargePower()); // 判断当前时间是否在时间段内
powerDataVo.setEndTime(DateUtils.parseDateToStr("HH:mm:ss",powerTemp.getEndTime())); if (!isTimeInRange(LocalTime.now(), emsStrategyTemp.getStartTime(), emsStrategyTemp.getEndTime())) {
powerDataVo.setStartTime(DateUtils.parseDateToStr("HH:mm:ss",powerTemp.getStartTime())); logger.info("当前站点: {}, 策略: {}, 时间段:{} 不在时间段内", siteId, strategyId, emsStrategyTemp.getStartTime() + "-" + emsStrategyTemp.getEndTime());
powerDataVoList.add(powerDataVo); continue;
}
// 查询PCS设备信息
EmsDevicesSetting queryDevices = new EmsDevicesSetting();
queryDevices.setSiteId(siteId);
queryDevices.setDeviceCategory(DeviceCategory.PCS.getCode());
List<EmsDevicesSetting> pcsDeviceList = emsDevicesMapper.selectEmsDevicesSettingList(queryDevices);
if (CollectionUtils.isEmpty(pcsDeviceList)) {
logger.info("当前站点: {} 未配置PCS设备", siteId);
continue;
}
// 判断SOC上下限
if (isSocInRange(emsStrategyTemp, runtimeConfig)) {
ProtectionConstraintVo protectionConstraint = getProtectionConstraint(siteId);
Map<Long, EmsPcsSetting> pcsSettingCache = new HashMap<>();
BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower()
.divide(new BigDecimal(pcsDeviceList.size()), POWER_SCALE, RoundingMode.HALF_UP);
BigDecimal totalActivePower = null;
if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
// 同一站点同一轮执行只读取一次电网电表,降低重复查库和数据抖动
EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name());
if (emsAmmeterData != null) {
totalActivePower = emsAmmeterData.getTotalActivePower();
} }
curve.setPowerData(powerDataVoList !=null ? JSON.toJSON(powerDataVoList).toString() : "");
// 记录推送记录
emsStrategyCurveMapper.insertEmsStrategyCurve(curve);
// 设置已下发
timeConfig.setIsPost(0);
emsStrategyTimeConfigMapper.updateEmsStrategyTimeConfig(timeConfig);
} }
for (EmsDevicesSetting pcsDevice : pcsDeviceList) {
EmsPcsSetting pcsSetting = pcsSettingCache.computeIfAbsent(
pcsDevice.getId(),
id -> emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(id)
);
if (pcsSetting == null || pcsSetting.getClusterNum() < 1) {
logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId());
continue;
}
// 平均功率值根据倍率放大后,再按电池簇数量平均分配
BigDecimal strategyPower = avgChargeDischargePower.multiply(runtimeConfig.getPowerSetMultiplier())
.divide(new BigDecimal(pcsSetting.getClusterNum()), POWER_SCALE, RoundingMode.HALF_UP);
// 根据充电状态,处理数据
if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
StrategyCommandDecision decision = applyProtectionConstraint(
strategyPower,
ChargeStatus.CHARGING,
runtimeConfig,
protectionConstraint
);
// 发送Modbus命令控制设备-充电
sendModbusCommand(
Collections.singletonList(pcsDevice),
pcsSetting,
decision.getChargeStatus(),
decision.getPower(),
emsStrategyTemp,
false,
null
);
} else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
boolean needAntiReverseFlow = false;
Integer powerDownType = null;
BigDecimal chargeDischargePower = strategyPower;
// 查询策略运行日志
EmsStrategyLog lastStrategyLog = getLastStrategyLog(pcsDevice.getDeviceId(), emsStrategyTemp);
if (lastStrategyLog != null) {
// 如果当前时间段已经进入待机状态,则不处理
if (ChargeStatus.STANDBY.getCode().equals(lastStrategyLog.getChargeStatus())) {
continue;
}
chargeDischargePower = lastStrategyLog.getChargeDischargePower();
powerDownType = lastStrategyLog.getPowerDownType();
}
// 查询电网电表的正向有功功率,36kW-50kW范围内稳定运行低于36kW降功率高于50kW增加功率
if (totalActivePower == null) {
logger.warn("当前站点: {}, 未获取到最新电表数据,执行保守策略并切换待机", emsStrategyTemp.getSiteId());
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, true, 0);
continue;
} else {
// 电网功率过低,直接待机,不再放电
if (totalActivePower.compareTo(runtimeConfig.getAntiReverseHardStopThreshold()) < 0) {
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, true, 0);
continue;
}
// 放电开始先按差值限幅:差值=电网功率-防逆流阈值
BigDecimal diffPower = totalActivePower.subtract(runtimeConfig.getAntiReverseThreshold());
BigDecimal targetPower = diffPower.compareTo(BigDecimal.ZERO) > 0 ? diffPower : BigDecimal.ZERO;
if (targetPower.compareTo(strategyPower) > 0) {
targetPower = strategyPower;
}
if (chargeDischargePower.compareTo(targetPower) > 0) {
chargeDischargePower = targetPower;
}
// 判断是否需要防逆流
needAntiReverseFlow = isNeedAntiReverseFlow(totalActivePower, runtimeConfig);
BigDecimal power = strategyPower.multiply(runtimeConfig.getAntiReversePowerDownPercent())
.divide(new BigDecimal(100), POWER_SCALE, RoundingMode.HALF_UP);
if (needAntiReverseFlow) {
// 降功率
chargeDischargePower = chargeDischargePower.subtract(power);
powerDownType = 0;
} else {
// 判断是否需要增加功率,
if (powerDownType != null && totalActivePower.compareTo(runtimeConfig.getAntiReverseUp()) > 0) {
if (chargeDischargePower.compareTo(targetPower) >= 0) {
// 功率增加到限幅值则停止
continue;
}
// 增加功率
chargeDischargePower = chargeDischargePower.add(power);
if (chargeDischargePower.compareTo(targetPower) > 0) {
chargeDischargePower = targetPower;
}
powerDownType = 1;
needAntiReverseFlow = true;
}
}
}
if (chargeDischargePower.compareTo(BigDecimal.ZERO) < 0) {
chargeDischargePower = BigDecimal.ZERO;
}
StrategyCommandDecision decision = applyProtectionConstraint(
chargeDischargePower,
ChargeStatus.DISCHARGING,
runtimeConfig,
protectionConstraint
);
ChargeStatus finalStatus = decision.getChargeStatus();
BigDecimal finalPower = decision.getPower();
if (ChargeStatus.STANDBY.equals(finalStatus) || BigDecimal.ZERO.compareTo(finalPower) == 0) {
// 如果已经降功率到0则设备直接待机
// 发送Modbus命令控制设备-待机
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow, powerDownType);
} else {
// 发送Modbus命令控制设备-放电
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, finalStatus, finalPower, emsStrategyTemp, needAntiReverseFlow, powerDownType);
}
} else {
// 发送Modbus命令控制设备-待机
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, false, null);
}
}
} else {
// 发送Modbus命令控制设备-待机
sendModbusCommand(pcsDeviceList, null, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, false, null);
} }
} }
} }
} }
private String[] dealWithMonth(int month) {
// 获取当前年份
int currentYear = LocalDate.now().getYear();
// 创建YearMonth对象表示当年指定的月份
YearMonth yearMonth = YearMonth.of(currentYear, month);
// 获取当月的第一天和最后一天
LocalDate firstDay = yearMonth.atDay(1);
LocalDate lastDay = yearMonth.atEndOfMonth();
// 定义日期格式(年月日)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 格式化日期
return new String[]{
firstDay.format(formatter),
lastDay.format(formatter)
};
private void saveStrategyLog(String deviceId, BigDecimal chargeDischargePower, String chargeStatus,
EmsStrategyTemp strategyTemp, boolean needAntiReverseFlow, Integer powerDownType) {
EmsStrategyLog log = new EmsStrategyLog();
log.setStrategyId(strategyTemp.getStrategyId());
log.setTemplateId(strategyTemp.getTemplateId());
log.setSiteId(strategyTemp.getSiteId());
log.setDeviceId(deviceId);
log.setStartTime(strategyTemp.getStartTime());
log.setEndTime(strategyTemp.getEndTime());
log.setChargeDischargePower(chargeDischargePower);
log.setChargeStatus(chargeStatus);
log.setExecutionDate(DateUtils.toDate(LocalDateTime.now()));
log.setAntiReverse(needAntiReverseFlow ? 1 : 0);
log.setPowerDownType(powerDownType);
emsStrategyLogMapper.insertEmsStrategyLog(log);
}
private List<EmsStrategyLog> getStrategyLog(String deviceId, String chargeStatus,
EmsStrategyTemp strategyTemp, boolean needAntiReverseFlow) {
EmsStrategyLog query = new EmsStrategyLog();
query.setStrategyId(strategyTemp.getStrategyId());
query.setTemplateId(strategyTemp.getTemplateId());
query.setSiteId(strategyTemp.getSiteId());
query.setDeviceId(deviceId);
query.setStartTime(strategyTemp.getStartTime());
query.setEndTime(strategyTemp.getEndTime());
query.setChargeStatus(chargeStatus);
query.setExecutionDate(DateUtils.toDate(LocalDateTime.now()));
query.setAntiReverse(needAntiReverseFlow ? 1 : 0);
return emsStrategyLogMapper.selectEmsStrategyLogList(query);
} }
} private EmsStrategyLog getLastStrategyLog(String deviceId, EmsStrategyTemp strategyTemp) {
EmsStrategyLog query = new EmsStrategyLog();
query.setStrategyId(strategyTemp.getStrategyId());
query.setTemplateId(strategyTemp.getTemplateId());
query.setSiteId(strategyTemp.getSiteId());
query.setDeviceId(deviceId);
query.setStartTime(strategyTemp.getStartTime());
query.setEndTime(strategyTemp.getEndTime());
query.setExecutionDate(DateUtils.toDate(LocalDateTime.now()));
return emsStrategyLogMapper.getLastStrategyLog(query);
}
private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower, EmsStrategyRuntimeConfig runtimeConfig) {
// 获取当前设定的防逆流阈值(30kW)
BigDecimal threshold = runtimeConfig.getAntiReverseThreshold();
// 计算20%范围的上限(36kW)
BigDecimal upperLimit = threshold.multiply(runtimeConfig.getAntiReverseRangePercent()).divide(new BigDecimal(100)).add(threshold);
// 判断电网电表正向有功功率是否小于36kW(接近30kW的20%范围)
return totalActivePower.compareTo(upperLimit) < 0;
}
public List<WriteTagConfig> getSwitchDeviceWriteTags(EmsPcsSetting pcsSetting, String workStatus) {
List<WriteTagConfig> writeTags = new ArrayList<>();
BigDecimal power;
if (WorkStatus.NORMAL.getCode().equals(workStatus)) {
// 开机先发送开机指令再发送有功功率给定值
WriteTagConfig writeTag = new WriteTagConfig();
writeTag.setAddress(pcsSetting.getPointAddress());
writeTag.setValue(pcsSetting.getStartCommand());
writeTags.add(writeTag);
power = pcsSetting.getStartPower();
} else {
// 关机
power = pcsSetting.getStopPower();
}
JSONArray array = JSON.parseArray(pcsSetting.getClusterPointAddress());
for (int i = 0; i < pcsSetting.getClusterNum(); i++) {
Object clusterPointAddress = array.get(i);
WriteTagConfig clusterWriteTag = new WriteTagConfig();
clusterWriteTag.setAddress(String.valueOf(clusterPointAddress));
// 电池簇PCS有功功率给定默认置0
if (power == null) {
power = BigDecimal.ZERO;
}
clusterWriteTag.setValue(power);
writeTags.add(clusterWriteTag);
}
if (WorkStatus.STOP.getCode().equals(workStatus)) {
// 关机先发送有功功率给定值再发送关机指令
WriteTagConfig writeTag = new WriteTagConfig();
writeTag.setAddress(pcsSetting.getPointAddress());
writeTag.setValue(pcsSetting.getStopCommand());
writeTags.add(writeTag);
}
return writeTags;
}
public List<WriteTagConfig> getWriteTags(EmsPcsSetting pcsSetting, BigDecimal chargeDischargePower) {
List<WriteTagConfig> writeTags = new ArrayList<>();
JSONArray array = JSON.parseArray(pcsSetting.getClusterPointAddress());
for (int i = 0; i < pcsSetting.getClusterNum(); i++) {
Object clusterPointAddress = array.get(i);
WriteTagConfig clusterWriteTag = new WriteTagConfig();
clusterWriteTag.setAddress(String.valueOf(clusterPointAddress));
clusterWriteTag.setValue(chargeDischargePower);
writeTags.add(clusterWriteTag);
}
return writeTags;
}
public DeviceConfig getDeviceConfig(String siteId, String deviceId, EmsDevicesSetting device, EmsPcsSetting pcsSetting, BigDecimal chargeDischargePower, int writeType) {
if (Objects.isNull(pcsSetting)) {
pcsSetting = emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(device.getId());
if (pcsSetting == null) {
logger.info("当前站点: {}, PCS设备: {} 未找到对应PCS配置", siteId, deviceId);
return null;
}
}
if (device.getIpPort() == null || device.getSlaveId() == null) {
logger.info("当前站点: {}, PCS设备: {} 未配置IP端口或从站号", siteId, deviceId);
return null;
}
DeviceConfig deviceConfig = new DeviceConfig();
deviceConfig.setDeviceNumber(device.getDeviceId());
deviceConfig.setDeviceName(device.getDeviceName());
deviceConfig.setSlaveId(device.getSlaveId().intValue());
deviceConfig.setHost(device.getIpAddress());
deviceConfig.setPort(device.getIpPort().intValue());
deviceConfig.setWriteTags(writeType == 0 ? getWriteTags(pcsSetting, chargeDischargePower) : getSwitchDeviceWriteTags(pcsSetting, device.getWorkStatus()));
return deviceConfig;
}
private void sendModbusCommand(List<EmsDevicesSetting> pcsDeviceList, EmsPcsSetting pcsSetting, ChargeStatus chargeStatus, BigDecimal chargeDischargePower,
EmsStrategyTemp emsStrategyTemp, boolean needAntiReverseFlow, Integer powerDownType) {
for (EmsDevicesSetting pcsDevice : pcsDeviceList) {
String siteId = pcsDevice.getSiteId();
String deviceId = pcsDevice.getDeviceId();
List<EmsStrategyLog> strategyLogList = getStrategyLog(deviceId, chargeStatus.getCode(), emsStrategyTemp, needAntiReverseFlow);
if (CollectionUtils.isNotEmpty(strategyLogList)) {
boolean isExist = true;
if (ChargeStatus.DISCHARGING.equals(chargeStatus) && needAntiReverseFlow) {
isExist = false;
}
if (isExist) {
logger.info("当前站点: {}, PCS设备: {} 当前时间段已存在策略执行记录,不再重复执行", siteId, deviceId);
continue;
}
}
// 每次操作先判断设备工作状态
if (StringUtils.isEmpty(pcsDevice.getWorkStatus()) || WorkStatus.ABNORMAL.getCode().equals(pcsDevice.getWorkStatus())) {
// 设备故障,不发送指令
continue;
} else if (WorkStatus.STOP.getCode().equals(pcsDevice.getWorkStatus())) {
// 设备停机
if (ChargeStatus.STANDBY.equals(chargeStatus)) {
// 待机,则不写入功率值
continue;
} else {
// 充、放电,则先开机设备
if (!switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL)) {
continue;
}
}
}
DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting, chargeDischargePower, 0);
if (deviceConfig == null) {
continue;
}
boolean result = modbusProcessor.writeDataToDeviceWithRetry(deviceConfig);
if (!result) {
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, chargeStatus.getInfo());
recordDeviceOperationLog(siteId, deviceId, "写功率", chargeDischargePower, false, chargeStatus.getInfo() + "功率下发失败");
continue;
} else {
recordDeviceOperationLog(siteId, deviceId, "写功率", chargeDischargePower, true, null);
if (ChargeStatus.STANDBY.equals(chargeStatus)) {
// 待机,先写功率值,再关机
if (!switchDevice(pcsDevice, pcsSetting, WorkStatus.STOP)) {
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, WorkStatus.STOP.getInfo());
continue;
}
}
}
// 记录策略执行日志
saveStrategyLog(deviceId, chargeDischargePower, chargeStatus.getCode(), emsStrategyTemp, needAntiReverseFlow, powerDownType);
}
}
//设备开关机
private boolean switchDevice(EmsDevicesSetting pcsDevice, EmsPcsSetting pcsSetting, WorkStatus workStatus) {
String siteId = pcsDevice.getSiteId();
String deviceId = pcsDevice.getDeviceId();
String originalWorkStatus = pcsDevice.getWorkStatus();
pcsDevice.setWorkStatus(workStatus.getCode());
DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting , null, 1);
if (deviceConfig == null) {
pcsDevice.setWorkStatus(originalWorkStatus);
return false;
}
boolean result = modbusProcessor.writeDataToDeviceWithRetry(deviceConfig);
if (!result) {
pcsDevice.setWorkStatus(originalWorkStatus);
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, workStatus.getInfo());
recordDeviceOperationLog(siteId, deviceId, "开关机", workStatus.getInfo(), false, workStatus.getInfo() + "指令发送失败");
} else {
recordDeviceOperationLog(siteId, deviceId, "开关机", workStatus.getInfo(), true, null);
}
return result;
}
private void recordDeviceOperationLog(String siteId, String deviceId, String action, Object param, boolean success, String errorMsg) {
try {
SysOperLog operLog = new SysOperLog();
operLog.setTitle("策略设备控制-" + action);
operLog.setBusinessType(BusinessType.UPDATE.ordinal());
operLog.setMethod(this.getClass().getName() + "." + action);
operLog.setRequestMethod("SCHEDULE");
operLog.setOperatorType(OperatorType.OTHER.ordinal());
operLog.setOperName("system");
operLog.setOperIp("127.0.0.1");
operLog.setOperUrl("/quartz/strategyPoller");
operLog.setOperTime(DateUtils.getNowDate());
Map<String, Object> operParam = new HashMap<>();
operParam.put("siteId", siteId);
operParam.put("deviceId", deviceId);
operParam.put("action", action);
operParam.put("param", param);
operLog.setOperParam(StringUtils.substring(JSON.toJSONString(operParam), 0, 2000));
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(Collections.singletonMap("success", success)), 0, 2000));
operLog.setStatus(success ? BusinessStatus.SUCCESS.ordinal() : BusinessStatus.FAIL.ordinal());
if (!success) {
operLog.setErrorMsg(StringUtils.substring(errorMsg, 0, 2000));
}
operLogService.insertOperlog(operLog);
} catch (Exception e) {
logger.error("记录sys_oper_log失败, siteId={}, deviceId={}, action={}", siteId, deviceId, action, e);
}
}
private ProtectionConstraintVo getProtectionConstraint(String siteId) {
ProtectionConstraintVo constraint = redisCache.getCacheObject(RedisKeyConstants.PROTECTION_CONSTRAINT + siteId);
if (constraint == null) {
return ProtectionConstraintVo.empty();
}
if (constraint.getPowerLimitRatio() == null) {
constraint.setPowerLimitRatio(DEFAULT_PROTECTION_RATIO);
}
if (constraint.getAllowCharge() == null) {
constraint.setAllowCharge(true);
}
if (constraint.getAllowDischarge() == null) {
constraint.setAllowDischarge(true);
}
if (constraint.getForceStandby() == null) {
constraint.setForceStandby(false);
}
if (constraint.getForceStop() == null) {
constraint.setForceStop(false);
}
if (constraint.getLevel() == null) {
constraint.setLevel(0);
}
return constraint;
}
private StrategyCommandDecision applyProtectionConstraint(BigDecimal targetPower,
ChargeStatus targetStatus,
EmsStrategyRuntimeConfig runtimeConfig,
ProtectionConstraintVo constraint) {
if (constraint == null || nullSafeInt(constraint.getLevel()) <= 0) {
return new StrategyCommandDecision(targetStatus, safePower(targetPower));
}
if (Boolean.TRUE.equals(constraint.getForceStop()) || Boolean.TRUE.equals(constraint.getForceStandby())) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
if (ChargeStatus.CHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowCharge())) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
if (ChargeStatus.DISCHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowDischarge())) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
BigDecimal ratio = getPowerLimitRatio(constraint, runtimeConfig);
BigDecimal finalPower = safePower(targetPower).multiply(ratio).setScale(POWER_SCALE, RoundingMode.HALF_UP);
if (finalPower.compareTo(BigDecimal.ZERO) <= 0) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
return new StrategyCommandDecision(targetStatus, finalPower);
}
private BigDecimal getPowerLimitRatio(ProtectionConstraintVo constraint, EmsStrategyRuntimeConfig runtimeConfig) {
BigDecimal ratio = constraint.getPowerLimitRatio();
if (ratio == null || ratio.compareTo(BigDecimal.ZERO) < 0 || ratio.compareTo(BigDecimal.ONE) > 0) {
ratio = DEFAULT_PROTECTION_RATIO;
}
if (nullSafeInt(constraint.getLevel()) == 1 && DEFAULT_PROTECTION_RATIO.compareTo(ratio) == 0) {
BigDecimal deratePercent = runtimeConfig.getProtectL1DeratePercent();
if (deratePercent == null || deratePercent.compareTo(BigDecimal.ZERO) < 0 || deratePercent.compareTo(new BigDecimal("100")) > 0) {
deratePercent = DEFAULT_PROTECT_L1_DERATE_PERCENT;
}
ratio = deratePercent.divide(new BigDecimal("100"), POWER_SCALE, RoundingMode.HALF_UP);
}
return ratio;
}
private BigDecimal safePower(BigDecimal power) {
if (power == null || power.compareTo(BigDecimal.ZERO) < 0) {
return BigDecimal.ZERO;
}
return power;
}
private int nullSafeInt(Integer value) {
return value == null ? 0 : value;
}
private static class StrategyCommandDecision {
private final ChargeStatus chargeStatus;
private final BigDecimal power;
private StrategyCommandDecision(ChargeStatus chargeStatus, BigDecimal power) {
this.chargeStatus = chargeStatus;
this.power = power;
}
public ChargeStatus getChargeStatus() {
return chargeStatus;
}
public BigDecimal getPower() {
return power;
}
}
// 判断当前时间是否在时间范围内
private static boolean isTimeInRange(LocalTime now, Date startTime, Date endTime) {
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
LocalTime startLocalTime = startTime.toInstant()
.atZone(zoneId)
.toLocalTime();
LocalTime endLocalTime = endTime.toInstant()
.atZone(zoneId)
.toLocalTime();
// 支持跨天时段如23:00-01:00边界采用闭区间
if (!startLocalTime.isAfter(endLocalTime)) {
return !now.isBefore(startLocalTime) && !now.isAfter(endLocalTime);
}
return !now.isBefore(startLocalTime) || !now.isAfter(endLocalTime);
}
// 判断SOC上限和下限
private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp, EmsStrategyRuntimeConfig runtimeConfig) {
BigDecimal socDown = runtimeConfig.getSocDown();
BigDecimal socUp = runtimeConfig.getSocUp();
if (SocLimit.ON.getCode().equals(emsStrategyTemp.getSdcLimit())) {
socDown = emsStrategyTemp.getSdcDown();
socUp = emsStrategyTemp.getSdcUp();
}
// 查询电池堆(BMSD) SOC
EmsBatteryStack emsBatteryStack = emsBatteryStackMapper.getSiteSumStackInfo(emsStrategyTemp.getSiteId());
if (emsBatteryStack == null || emsBatteryStack.getStackSoc() == null) {
return true;
}
// 充电阶段判断SOC上限值
if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus()) && emsBatteryStack.getStackSoc().compareTo(socUp) >= 0) {
return false;
}
// 放电阶段判断SOC下限值
if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus()) && emsBatteryStack.getStackSoc().compareTo(socDown) <= 0) {
return false;
}
return true;
}
private EmsStrategyRuntimeConfig getRuntimeConfig(String siteId) {
EmsStrategyRuntimeConfig config = runtimeConfigMapper.selectBySiteId(siteId);
if (config == null) {
config = new EmsStrategyRuntimeConfig();
config.setSiteId(siteId);
}
if (config.getSocDown() == null) {
config.setSocDown(DEFAULT_SOC_DOWN);
}
if (config.getSocUp() == null) {
config.setSocUp(DEFAULT_SOC_UP);
}
if (config.getAntiReverseThreshold() == null) {
config.setAntiReverseThreshold(DEFAULT_ANTI_REVERSE_THRESHOLD);
}
if (config.getAntiReverseRangePercent() == null) {
config.setAntiReverseRangePercent(DEFAULT_ANTI_REVERSE_RANGE_PERCENT);
}
if (config.getAntiReverseUp() == null) {
config.setAntiReverseUp(DEFAULT_ANTI_REVERSE_UP);
}
if (config.getAntiReversePowerDownPercent() == null) {
config.setAntiReversePowerDownPercent(DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT);
}
if (config.getAntiReverseHardStopThreshold() == null) {
config.setAntiReverseHardStopThreshold(DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD);
}
if (config.getPowerSetMultiplier() == null || config.getPowerSetMultiplier().compareTo(BigDecimal.ZERO) <= 0) {
config.setPowerSetMultiplier(DEFAULT_POWER_SET_MULTIPLIER);
}
if (config.getProtectInterveneEnable() == null) {
config.setProtectInterveneEnable(DEFAULT_PROTECT_INTERVENE_ENABLE);
}
if (config.getProtectL1DeratePercent() == null
|| config.getProtectL1DeratePercent().compareTo(BigDecimal.ZERO) < 0
|| config.getProtectL1DeratePercent().compareTo(new BigDecimal("100")) > 0) {
config.setProtectL1DeratePercent(DEFAULT_PROTECT_L1_DERATE_PERCENT);
}
if (config.getProtectRecoveryStableSeconds() == null || config.getProtectRecoveryStableSeconds() < 0) {
config.setProtectRecoveryStableSeconds(DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS);
}
if (config.getProtectL3LatchEnable() == null) {
config.setProtectL3LatchEnable(DEFAULT_PROTECT_L3_LATCH_ENABLE);
}
if (StringUtils.isEmpty(config.getProtectConflictPolicy())) {
config.setProtectConflictPolicy(DEFAULT_PROTECT_CONFLICT_POLICY);
}
return config;
}
}

View File

@ -60,4 +60,25 @@ public class CronUtils
throw new IllegalArgumentException(e.getMessage()); throw new IllegalArgumentException(e.getMessage());
} }
} }
/**
* 返回当前时间到下一次执行时间间隔的毫秒
*/
public static long getNextExecutionIntervalMillis(String cronExpression)
{
try
{
CronExpression cron = new CronExpression(cronExpression);
Date now = new Date();
Date nextExecution = cron.getNextValidTimeAfter(now);
Date nextExecution2 = cron.getNextValidTimeAfter(nextExecution);
return nextExecution2.getTime() - nextExecution.getTime();
}
catch (ParseException e)
{
throw new IllegalArgumentException(e.getMessage());
}
}
} }

View File

@ -27,6 +27,12 @@
<artifactId>jaxb-runtime</artifactId> <artifactId>jaxb-runtime</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.influxdb</groupId>
<artifactId>influxdb-java</artifactId>
<version>2.24</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,61 @@
package com.xzzn.ems.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "weather.api")
public class WeatherApiProperties {
/**
* 是否启用天气同步
*/
private boolean enabled = false;
/**
* 天气接口基础地址
*/
private String baseUrl = "https://archive-api.open-meteo.com/v1/archive";
/**
* 可选接口鉴权字段
*/
private String apiKey;
/**
* 时区参数
*/
private String timezone = "Asia/Shanghai";
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getTimezone() {
return timezone;
}
public void setTimezone(String timezone) {
this.timezone = timezone;
}
}

View File

@ -1,14 +1,15 @@
package com.xzzn.ems.domain; package com.xzzn.ems.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity;
import com.xzzn.common.utils.StringUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.core.domain.BaseEntity;
import com.xzzn.common.utils.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.xzzn.common.annotation.Excel;
/** /**
* 电池簇数据对象 ems_battery_cluster * 电池簇数据对象 ems_battery_cluster
@ -28,8 +29,8 @@ public class EmsBatteryCluster extends BaseEntity
@Excel(name = "数据更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") @Excel(name = "数据更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date dataUpdateTime; private Date dataUpdateTime;
/** 工作状态0-正常 1-异常 2-停止 */ /** 工作状态0-运行 1-停机 2-故障 */
@Excel(name = "工作状态0-正常 1-异常 2-停止") @Excel(name = "工作状态0-运行 1-停机 2-故障")
private String workStatus; private String workStatus;
/** 与PCS通信状态0-正常 1-通信中断 2-异常 */ /** 与PCS通信状态0-正常 1-通信中断 2-异常 */

View File

@ -1,12 +1,14 @@
package com.xzzn.ems.domain; package com.xzzn.ems.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.xzzn.common.annotation.Excel;
/** /**
* 单体电池天级数据对象 ems_battery_data_day * 单体电池天级数据对象 ems_battery_data_day
@ -75,6 +77,10 @@ public class EmsBatteryDataDay extends BaseEntity
@Excel(name = "天级时间维度", width = 30, dateFormat = "yyyy-MM-dd") @Excel(name = "天级时间维度", width = 30, dateFormat = "yyyy-MM-dd")
private Date dayTime; private Date dayTime;
/** 单体电池电流 */
@Excel(name = "单体电池电流")
private BigDecimal current;
public void setId(Long id) public void setId(Long id)
{ {
this.id = id; this.id = id;
@ -215,6 +221,14 @@ public class EmsBatteryDataDay extends BaseEntity
return dayTime; return dayTime;
} }
public BigDecimal getCurrent() {
return current;
}
public void setCurrent(BigDecimal current) {
this.current = current;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -237,6 +251,7 @@ public class EmsBatteryDataDay extends BaseEntity
.append("clusterDeviceId", getClusterDeviceId()) .append("clusterDeviceId", getClusterDeviceId())
.append("interResistance", getInterResistance()) .append("interResistance", getInterResistance())
.append("dayTime", getDayTime()) .append("dayTime", getDayTime())
.append("current", getCurrent())
.toString(); .toString();
} }
} }

View File

@ -1,13 +1,14 @@
package com.xzzn.ems.domain; package com.xzzn.ems.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.xzzn.common.annotation.Excel;
/** /**
* 电池堆数据对象 ems_battery_stack * 电池堆数据对象 ems_battery_stack
@ -27,8 +28,8 @@ public class EmsBatteryStack extends BaseEntity
@Excel(name = "数据更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") @Excel(name = "数据更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date dataUpdateTime; private Date dataUpdateTime;
/** 工作状态0-正常 1-异常 2-停止 */ /** 工作状态0-运行 1-停机 2-故障 */
@Excel(name = "工作状态0-正常 1-异常 2-停止") @Excel(name = "工作状态0-运行 1-停机 2-故障")
private String workStatus; private String workStatus;
/** 与PCS通信状态0-正常 1-通信中断 2-异常 */ /** 与PCS通信状态0-正常 1-通信中断 2-异常 */

View File

@ -25,10 +25,6 @@ public class EmsDailyChargeData extends BaseEntity
@Excel(name = "站点id") @Excel(name = "站点id")
private String siteId; private String siteId;
/** 设备唯一标识符 */
@Excel(name = "设备唯一标识符")
private String deviceId;
/** 数据日期:yyyy-MM-dd */ /** 数据日期:yyyy-MM-dd */
@JsonFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "数据日期:yyyy-MM-dd", width = 30, dateFormat = "yyyy-MM-dd") @Excel(name = "数据日期:yyyy-MM-dd", width = 30, dateFormat = "yyyy-MM-dd")
@ -50,6 +46,14 @@ public class EmsDailyChargeData extends BaseEntity
@Excel(name = "当日放电量") @Excel(name = "当日放电量")
private BigDecimal dischargeData; private BigDecimal dischargeData;
/** 总收入 */
@Excel(name = "总收入")
private BigDecimal totalRevenue;
/** 当日实时收入 */
@Excel(name = "当日实时收入")
private BigDecimal dayRevenue;
public void setId(Long id) public void setId(Long id)
{ {
this.id = id; this.id = id;
@ -70,16 +74,6 @@ public class EmsDailyChargeData extends BaseEntity
return siteId; return siteId;
} }
public void setDeviceId(String deviceId)
{
this.deviceId = deviceId;
}
public String getDeviceId()
{
return deviceId;
}
public void setDateTime(Date dateTime) public void setDateTime(Date dateTime)
{ {
this.dateTime = dateTime; this.dateTime = dateTime;
@ -130,17 +124,38 @@ public class EmsDailyChargeData extends BaseEntity
return dischargeData; return dischargeData;
} }
public void setTotalRevenue(BigDecimal totalRevenue)
{
this.totalRevenue = totalRevenue;
}
public BigDecimal getTotalRevenue()
{
return totalRevenue;
}
public void setDayRevenue(BigDecimal dayRevenue)
{
this.dayRevenue = dayRevenue;
}
public BigDecimal getDayRevenue()
{
return dayRevenue;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId()) .append("id", getId())
.append("siteId", getSiteId()) .append("siteId", getSiteId())
.append("deviceId", getDeviceId())
.append("dateTime", getDateTime()) .append("dateTime", getDateTime())
.append("totalChargeData", getTotalChargeData()) .append("totalChargeData", getTotalChargeData())
.append("totalDischargeData", getTotalDischargeData()) .append("totalDischargeData", getTotalDischargeData())
.append("chargeData", getChargeData()) .append("chargeData", getChargeData())
.append("dischargeData", getDischargeData()) .append("dischargeData", getDischargeData())
.append("totalRevenue", getTotalRevenue())
.append("dayRevenue", getDayRevenue())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())
.append("updateBy", getUpdateBy()) .append("updateBy", getUpdateBy())

View File

@ -30,13 +30,9 @@ public class EmsDailyEnergyData extends BaseEntity
@Excel(name = "数据日期:yyyy-MM-dd", width = 30, dateFormat = "yyyy-MM-dd") @Excel(name = "数据日期:yyyy-MM-dd", width = 30, dateFormat = "yyyy-MM-dd")
private Date dataDate; private Date dataDate;
/** 总收入 */ /** 数据小时(0-23) */
@Excel(name = "总收入") @Excel(name = "数据小时(0-23)")
private BigDecimal totalRevenue; private Integer dataHour;
/** 当日实时收入 */
@Excel(name = "当日实时收入")
private BigDecimal dayRevenue;
/** 尖峰时段充电差值 */ /** 尖峰时段充电差值 */
@Excel(name = "尖峰时段充电差值") @Excel(name = "尖峰时段充电差值")
@ -71,6 +67,7 @@ public class EmsDailyEnergyData extends BaseEntity
private BigDecimal valleyDischargeDiff; private BigDecimal valleyDischargeDiff;
/** 差值计算时间如2025-10-10 23:59:00 */ /** 差值计算时间如2025-10-10 23:59:00 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "差值计算时间", readConverterExp = "如=2025-10-10,2=3:59:00") @Excel(name = "差值计算时间", readConverterExp = "如=2025-10-10,2=3:59:00")
private Date calcTime; private Date calcTime;
@ -104,24 +101,14 @@ public class EmsDailyEnergyData extends BaseEntity
return dataDate; return dataDate;
} }
public void setTotalRevenue(BigDecimal totalRevenue) public void setDataHour(Integer dataHour)
{ {
this.totalRevenue = totalRevenue; this.dataHour = dataHour;
} }
public BigDecimal getTotalRevenue() public Integer getDataHour()
{ {
return totalRevenue; return dataHour;
}
public void setDayRevenue(BigDecimal dayRevenue)
{
this.dayRevenue = dayRevenue;
}
public BigDecimal getDayRevenue()
{
return dayRevenue;
} }
public void setPeakChargeDiff(BigDecimal peakChargeDiff) public void setPeakChargeDiff(BigDecimal peakChargeDiff)
@ -220,8 +207,7 @@ public class EmsDailyEnergyData extends BaseEntity
.append("id", getId()) .append("id", getId())
.append("siteId", getSiteId()) .append("siteId", getSiteId())
.append("dataDate", getDataDate()) .append("dataDate", getDataDate())
.append("totalRevenue", getTotalRevenue()) .append("dataHour", getDataHour())
.append("dayRevenue", getDayRevenue())
.append("peakChargeDiff", getPeakChargeDiff()) .append("peakChargeDiff", getPeakChargeDiff())
.append("peakDischargeDiff", getPeakDischargeDiff()) .append("peakDischargeDiff", getPeakDischargeDiff())
.append("highChargeDiff", getHighChargeDiff()) .append("highChargeDiff", getHighChargeDiff())

View File

@ -1,11 +1,13 @@
package com.xzzn.ems.domain; package com.xzzn.ems.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity; import com.xzzn.common.core.domain.BaseEntity;
import java.util.Date;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.xzzn.common.annotation.Excel;
/** /**
* Modbus设备配置对象 ems_devices_setting * Modbus设备配置对象 ems_devices_setting
@ -102,10 +104,14 @@ public class EmsDevicesSetting extends BaseEntity
@Excel(name = "设备类别例如“STACK/CLUSTER/PCS等”") @Excel(name = "设备类别例如“STACK/CLUSTER/PCS等”")
private String deviceCategory; private String deviceCategory;
/** 设备运行状态0-离线、1-待机、2-运行、3-故障、4-停机 */ /** 设备运行状态0-离线、1-在线 */
@Excel(name = "设备运行状态0-离线、1-待机、2-运行、3-故障、4-停机") @Excel(name = "设备运行状态0-离线、1-在线")
private String deviceStatus; private String deviceStatus;
/** 设备运行状态0-离线、1-在线 */
@Excel(name = "工作状态0-运行 1-停机 2-故障")
private String workStatus;
/** 设备图像地址 */ /** 设备图像地址 */
@Excel(name = "设备图像地址") @Excel(name = "设备图像地址")
private String pictureUrl; private String pictureUrl;
@ -328,6 +334,14 @@ public class EmsDevicesSetting extends BaseEntity
this.deviceStatus = deviceStatus; this.deviceStatus = deviceStatus;
} }
public String getWorkStatus() {
return workStatus;
}
public void setWorkStatus(String workStatus) {
this.workStatus = workStatus;
}
public void setPictureUrl(String pictureUrl) public void setPictureUrl(String pictureUrl)
{ {
this.pictureUrl = pictureUrl; this.pictureUrl = pictureUrl;

View File

@ -28,16 +28,16 @@ public class EmsPcsData extends BaseEntity
@Excel(name = "数据更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") @Excel(name = "数据更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date dataUpdateTime; private Date dataUpdateTime;
/** 工作状态0-正常 1-异常 2-停止 */ /** 工作状态0-运行 1-停机 2-故障 */
@Excel(name = "工作状态0-正常 1-异常 2-停止") @Excel(name = "工作状态0-运行 1-停机 2-故障")
private String workStatus; private String workStatus;
/** 并网状态0-并网 1-未并网 */ /** 并网状态0-并网 1-未并网 */
@Excel(name = "并网状态0-并网 1-未并网") @Excel(name = "并网状态0-并网 1-未并网")
private String gridStatus; private String gridStatus;
/** 设备运行状态0-离线、1-待机、2-运行、3-故障、4-停机 */ /** 设备运行状态0-离线、1-在线 */
@Excel(name = "设备运行状态0-离线、1-待机、2-运行、3-故障、4-停机") @Excel(name = "设备运行状态0-离线、1-在线")
private String deviceStatus; private String deviceStatus;
/** 控制模式0-远程 1-本地 */ /** 控制模式0-远程 1-本地 */
@ -236,22 +236,6 @@ public class EmsPcsData extends BaseEntity
@Excel(name = "4#模块IGBT最高温") @Excel(name = "4#模块IGBT最高温")
private BigDecimal module4Temp; private BigDecimal module4Temp;
/** 电池簇1PCS有功功率给定 */
@Excel(name = "电池簇1PCS有功功率给定")
private BigDecimal cluster1ActivePower;
/** 电池簇2PCS有功功率给定 */
@Excel(name = "电池簇2PCS有功功率给定")
private BigDecimal cluster2ActivePower;
/** 电池簇3PCS有功功率给定 */
@Excel(name = "电池簇3PCS有功功率给定")
private BigDecimal cluster3ActivePower;
/** 电池簇4PCS有功功率给定 */
@Excel(name = "电池簇4PCS有功功率给定")
private BigDecimal cluster4ActivePower;
public void setId(Long id) public void setId(Long id)
{ {
this.id = id; this.id = id;
@ -792,38 +776,6 @@ public class EmsPcsData extends BaseEntity
return module4Temp; return module4Temp;
} }
public BigDecimal getCluster1ActivePower() {
return cluster1ActivePower;
}
public void setCluster1ActivePower(BigDecimal cluster1ActivePower) {
this.cluster1ActivePower = cluster1ActivePower;
}
public BigDecimal getCluster2ActivePower() {
return cluster2ActivePower;
}
public void setCluster2ActivePower(BigDecimal cluster2ActivePower) {
this.cluster2ActivePower = cluster2ActivePower;
}
public BigDecimal getCluster3ActivePower() {
return cluster3ActivePower;
}
public void setCluster3ActivePower(BigDecimal cluster3ActivePower) {
this.cluster3ActivePower = cluster3ActivePower;
}
public BigDecimal getCluster4ActivePower() {
return cluster4ActivePower;
}
public void setCluster4ActivePower(BigDecimal cluster4ActivePower) {
this.cluster4ActivePower = cluster4ActivePower;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@ -886,10 +838,6 @@ public class EmsPcsData extends BaseEntity
.append("module2Temp", getModule2Temp()) .append("module2Temp", getModule2Temp())
.append("module3Temp", getModule3Temp()) .append("module3Temp", getModule3Temp())
.append("module4Temp", getModule4Temp()) .append("module4Temp", getModule4Temp())
.append("cluster1ActivePower", getCluster1ActivePower())
.append("cluster2ActivePower", getCluster2ActivePower())
.append("cluster3ActivePower", getCluster3ActivePower())
.append("cluster4ActivePower", getCluster4ActivePower())
.toString(); .toString();
} }
} }

View File

@ -0,0 +1,192 @@
package com.xzzn.ems.domain;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity;
import java.math.BigDecimal;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* PCS设备配置对象 ems_pcs_setting
*
* @author xzzn
* @date 2025-12-29
*/
public class EmsPcsSetting extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 设备ID主键自增长 */
private Long id;
/** 设备配置关联ID */
@Excel(name = "设备配置关联ID")
private Long deviceSettingId;
/** 开关机地址 */
@Excel(name = "开关机地址")
private String pointAddress;
/** 功率地址 */
@Excel(name = "功率地址")
private String powerAddress;
/** 开机指令 */
@Excel(name = "开机指令")
private String startCommand;
/** 关机指令 */
@Excel(name = "关机指令")
private String stopCommand;
/** 开机目标功率 */
@Excel(name = "开机目标功率")
private BigDecimal startPower;
/** 关机目标功率 */
@Excel(name = "关机目标功率")
private BigDecimal stopPower;
/** 目标功率倍率 */
@Excel(name = "目标功率倍率")
private BigDecimal powerMultiplier;
/** 电池簇数 */
@Excel(name = "电池簇数")
private Integer clusterNum;
/** 电池簇地址 */
@Excel(name = "电池簇地址")
private String clusterPointAddress;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setDeviceSettingId(Long deviceSettingId)
{
this.deviceSettingId = deviceSettingId;
}
public Long getDeviceSettingId()
{
return deviceSettingId;
}
public String getPowerAddress() {
return powerAddress;
}
public void setPowerAddress(String powerAddress) {
this.powerAddress = powerAddress;
}
public void setPointAddress(String pointAddress)
{
this.pointAddress = pointAddress;
}
public String getPointAddress()
{
return pointAddress;
}
public void setStartCommand(String startCommand)
{
this.startCommand = startCommand;
}
public String getStartCommand()
{
return startCommand;
}
public void setStopCommand(String stopCommand)
{
this.stopCommand = stopCommand;
}
public String getStopCommand()
{
return stopCommand;
}
public void setStartPower(BigDecimal startPower)
{
this.startPower = startPower;
}
public BigDecimal getStartPower()
{
return startPower;
}
public void setStopPower(BigDecimal stopPower)
{
this.stopPower = stopPower;
}
public BigDecimal getStopPower()
{
return stopPower;
}
public BigDecimal getPowerMultiplier()
{
return powerMultiplier;
}
public void setPowerMultiplier(BigDecimal powerMultiplier)
{
this.powerMultiplier = powerMultiplier;
}
public void setClusterNum(Integer clusterNum)
{
this.clusterNum = clusterNum;
}
public Integer getClusterNum()
{
return clusterNum;
}
public void setClusterPointAddress(String clusterPointAddress)
{
this.clusterPointAddress = clusterPointAddress;
}
public String getClusterPointAddress()
{
return clusterPointAddress;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("deviceSettingId", getDeviceSettingId())
.append("pointAddress", getPointAddress())
.append("startCommand", getStartCommand())
.append("stopCommand", getStopCommand())
.append("startPower", getStartPower())
.append("stopPower", getStopPower())
.append("powerMultiplier", getPowerMultiplier())
.append("clusterNum", getClusterNum())
.append("clusterPointAddress", getClusterPointAddress())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

View File

@ -0,0 +1,128 @@
package com.xzzn.ems.domain;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class EmsPointCalcConfig extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
@Excel(name = "点位ID")
private String pointId;
@Excel(name = "站点ID")
private String siteId;
@Excel(name = "设备类型")
private String deviceCategory;
@Excel(name = "点位名称")
private String pointName;
@Excel(name = "数据键")
private String dataKey;
@Excel(name = "点位描述")
private String pointDesc;
@Excel(name = "单位")
private String dataUnit;
@Excel(name = "计算表达式")
private String calcExpression;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getPointName() {
return pointName;
}
public void setPointName(String pointName) {
this.pointName = pointName;
}
public String getDeviceCategory() {
return deviceCategory;
}
public void setDeviceCategory(String deviceCategory) {
this.deviceCategory = deviceCategory;
}
public String getDataKey() {
return dataKey;
}
public void setDataKey(String dataKey) {
this.dataKey = dataKey;
}
public String getPointDesc() {
return pointDesc;
}
public void setPointDesc(String pointDesc) {
this.pointDesc = pointDesc;
}
public String getDataUnit() {
return dataUnit;
}
public void setDataUnit(String dataUnit) {
this.dataUnit = dataUnit;
}
public String getCalcExpression() {
return calcExpression;
}
public void setCalcExpression(String calcExpression) {
this.calcExpression = calcExpression;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("pointId", getPointId())
.append("siteId", getSiteId())
.append("deviceCategory", getDeviceCategory())
.append("pointName", getPointName())
.append("dataKey", getDataKey())
.append("pointDesc", getPointDesc())
.append("dataUnit", getDataUnit())
.append("calcExpression", getCalcExpression())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

View File

@ -0,0 +1,313 @@
package com.xzzn.ems.domain;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.math.BigDecimal;
/**
* 点位配置对象 ems_point_config
*/
public class EmsPointConfig extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
@Excel(name = "点位ID")
private String pointId;
@Excel(name = "站点ID")
private String siteId;
@Excel(name = "设备类型")
private String deviceCategory;
@Excel(name = "设备ID")
private String deviceId;
@Excel(name = "点位名称")
private String pointName;
@Excel(name = "数据键")
private String dataKey;
@Excel(name = "点位描述")
private String pointDesc;
@Excel(name = "寄存器地址")
private String registerAddress;
@Excel(name = "单位")
private String dataUnit;
@Excel(name = "A系数")
private BigDecimal dataA;
@Excel(name = "K系数")
private BigDecimal dataK;
@Excel(name = "B系数")
private BigDecimal dataB;
@Excel(name = "位偏移")
private Integer dataBit;
@Excel(name = "是否报警点位", readConverterExp = "0=否,1=是")
private Integer isAlarm;
@Excel(name = "点位类型")
private String pointType;
@Excel(name = "计算表达式")
private String calcExpression;
@Excel(name = "是否启用采集", readConverterExp = "0=否,1=是")
private Integer collectEnabled;
@Excel(name = "采集来源")
private String collectSource;
@Excel(name = "Modbus寄存器类型")
private String modbusRegisterType;
@Excel(name = "Modbus数据类型")
private String modbusDataType;
@Excel(name = "Modbus读取顺序")
private Integer modbusReadOrder;
@Excel(name = "Modbus分组")
private String modbusGroup;
@Excel(name = "小数位数")
private Integer decimalScale;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getDeviceCategory() {
return deviceCategory;
}
public void setDeviceCategory(String deviceCategory) {
this.deviceCategory = deviceCategory;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getPointName() {
return pointName;
}
public void setPointName(String pointName) {
this.pointName = pointName;
}
public String getDataKey() {
return dataKey;
}
public void setDataKey(String dataKey) {
this.dataKey = dataKey;
}
public String getPointDesc() {
return pointDesc;
}
public void setPointDesc(String pointDesc) {
this.pointDesc = pointDesc;
}
public String getRegisterAddress() {
return registerAddress;
}
public void setRegisterAddress(String registerAddress) {
this.registerAddress = registerAddress;
}
public String getDataUnit() {
return dataUnit;
}
public void setDataUnit(String dataUnit) {
this.dataUnit = dataUnit;
}
public BigDecimal getDataA() {
return dataA;
}
public void setDataA(BigDecimal dataA) {
this.dataA = dataA;
}
public BigDecimal getDataK() {
return dataK;
}
public void setDataK(BigDecimal dataK) {
this.dataK = dataK;
}
public BigDecimal getDataB() {
return dataB;
}
public void setDataB(BigDecimal dataB) {
this.dataB = dataB;
}
public Integer getDataBit() {
return dataBit;
}
public void setDataBit(Integer dataBit) {
this.dataBit = dataBit;
}
public Integer getIsAlarm() {
return isAlarm;
}
public void setIsAlarm(Integer isAlarm) {
this.isAlarm = isAlarm;
}
public String getPointType() {
return pointType;
}
public void setPointType(String pointType) {
this.pointType = pointType;
}
public String getCalcExpression() {
return calcExpression;
}
public void setCalcExpression(String calcExpression) {
this.calcExpression = calcExpression;
}
public Integer getCollectEnabled() {
return collectEnabled;
}
public void setCollectEnabled(Integer collectEnabled) {
this.collectEnabled = collectEnabled;
}
public String getCollectSource() {
return collectSource;
}
public void setCollectSource(String collectSource) {
this.collectSource = collectSource;
}
public String getModbusRegisterType() {
return modbusRegisterType;
}
public void setModbusRegisterType(String modbusRegisterType) {
this.modbusRegisterType = modbusRegisterType;
}
public String getModbusDataType() {
return modbusDataType;
}
public void setModbusDataType(String modbusDataType) {
this.modbusDataType = modbusDataType;
}
public Integer getModbusReadOrder() {
return modbusReadOrder;
}
public void setModbusReadOrder(Integer modbusReadOrder) {
this.modbusReadOrder = modbusReadOrder;
}
public String getModbusGroup() {
return modbusGroup;
}
public void setModbusGroup(String modbusGroup) {
this.modbusGroup = modbusGroup;
}
public Integer getDecimalScale() {
return decimalScale;
}
public void setDecimalScale(Integer decimalScale) {
this.decimalScale = decimalScale;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("pointId", getPointId())
.append("siteId", getSiteId())
.append("deviceCategory", getDeviceCategory())
.append("deviceId", getDeviceId())
.append("pointName", getPointName())
.append("dataKey", getDataKey())
.append("pointDesc", getPointDesc())
.append("registerAddress", getRegisterAddress())
.append("dataUnit", getDataUnit())
.append("dataA", getDataA())
.append("dataK", getDataK())
.append("dataB", getDataB())
.append("dataBit", getDataBit())
.append("isAlarm", getIsAlarm())
.append("pointType", getPointType())
.append("calcExpression", getCalcExpression())
.append("collectEnabled", getCollectEnabled())
.append("collectSource", getCollectSource())
.append("modbusRegisterType", getModbusRegisterType())
.append("modbusDataType", getModbusDataType())
.append("modbusReadOrder", getModbusReadOrder())
.append("modbusGroup", getModbusGroup())
.append("decimalScale", getDecimalScale())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

View File

@ -0,0 +1,58 @@
package com.xzzn.ems.domain;
import com.xzzn.common.core.domain.BaseEntity;
import java.util.Date;
/**
* 单站监控展示数据对象(按分组落到 ems_site_monitor_data_home/sbjk/tjbb
*/
public class EmsSiteMonitorData extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
private String siteId;
private String fieldCode;
private String fieldValue;
private Date valueTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getFieldCode() {
return fieldCode;
}
public void setFieldCode(String fieldCode) {
this.fieldCode = fieldCode;
}
public String getFieldValue() {
return fieldValue;
}
public void setFieldValue(String fieldValue) {
this.fieldValue = fieldValue;
}
public Date getValueTime() {
return valueTime;
}
public void setValueTime(Date valueTime) {
this.valueTime = valueTime;
}
}

View File

@ -0,0 +1,101 @@
package com.xzzn.ems.domain;
import com.xzzn.common.core.domain.BaseEntity;
/**
* 单站监控配置项定义对象 ems_site_monitor_item
*/
public class EmsSiteMonitorItem extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
private String moduleCode;
private String moduleName;
private String menuCode;
private String menuName;
private String sectionName;
private String fieldCode;
private String fieldName;
private Integer sortNo;
private Integer status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getModuleCode() {
return moduleCode;
}
public void setModuleCode(String moduleCode) {
this.moduleCode = moduleCode;
}
public String getModuleName() {
return moduleName;
}
public void setModuleName(String moduleName) {
this.moduleName = moduleName;
}
public String getMenuCode() {
return menuCode;
}
public void setMenuCode(String menuCode) {
this.menuCode = menuCode;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public String getSectionName() {
return sectionName;
}
public void setSectionName(String sectionName) {
this.sectionName = sectionName;
}
public String getFieldCode() {
return fieldCode;
}
public void setFieldCode(String fieldCode) {
this.fieldCode = fieldCode;
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public Integer getSortNo() {
return sortNo;
}
public void setSortNo(Integer sortNo) {
this.sortNo = sortNo;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}

View File

@ -0,0 +1,74 @@
package com.xzzn.ems.domain;
import com.xzzn.common.core.domain.BaseEntity;
/**
* 单站监控字段点位映射对象 ems_site_monitor_point_match
*/
public class EmsSiteMonitorPointMatch extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
private String siteId;
private String fieldCode;
private String deviceId;
private String dataPoint;
private String fixedDataPoint;
private Integer useFixedDisplay;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getFieldCode() {
return fieldCode;
}
public void setFieldCode(String fieldCode) {
this.fieldCode = fieldCode;
}
public String getDataPoint() {
return dataPoint;
}
public void setDataPoint(String dataPoint) {
this.dataPoint = dataPoint;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getFixedDataPoint() {
return fixedDataPoint;
}
public void setFixedDataPoint(String fixedDataPoint) {
this.fixedDataPoint = fixedDataPoint;
}
public Integer getUseFixedDisplay() {
return useFixedDisplay;
}
public void setUseFixedDisplay(Integer useFixedDisplay) {
this.useFixedDisplay = useFixedDisplay;
}
}

View File

@ -25,6 +25,10 @@ public class EmsSiteSetting extends BaseEntity
@Excel(name = "站点名称") @Excel(name = "站点名称")
private String siteName; private String siteName;
/** 站点简称 */
@Excel(name = "站点简称")
private String siteShortName;
/** 站点地址 */ /** 站点地址 */
@Excel(name = "站点地址") @Excel(name = "站点地址")
private String siteAddress; private String siteAddress;
@ -54,6 +58,9 @@ public class EmsSiteSetting extends BaseEntity
@Excel(name = "站点id") @Excel(name = "站点id")
private String siteId; private String siteId;
/** 授权状态true-已授权false-未授权 */
private Boolean authorized;
public void setId(Long id) public void setId(Long id)
{ {
this.id = id; this.id = id;
@ -74,6 +81,16 @@ public class EmsSiteSetting extends BaseEntity
return siteName; return siteName;
} }
public void setSiteShortName(String siteShortName)
{
this.siteShortName = siteShortName;
}
public String getSiteShortName()
{
return siteShortName;
}
public void setSiteAddress(String siteAddress) public void setSiteAddress(String siteAddress)
{ {
this.siteAddress = siteAddress; this.siteAddress = siteAddress;
@ -144,11 +161,22 @@ public class EmsSiteSetting extends BaseEntity
return siteId; return siteId;
} }
public void setAuthorized(Boolean authorized)
{
this.authorized = authorized;
}
public Boolean getAuthorized()
{
return authorized;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId()) .append("id", getId())
.append("siteName", getSiteName()) .append("siteName", getSiteName())
.append("siteShortName", getSiteShortName())
.append("siteAddress", getSiteAddress()) .append("siteAddress", getSiteAddress())
.append("runningTime", getRunningTime()) .append("runningTime", getRunningTime())
.append("latitude", getLatitude()) .append("latitude", getLatitude())
@ -161,6 +189,7 @@ public class EmsSiteSetting extends BaseEntity
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())
.append("updateTime", getUpdateTime()) .append("updateTime", getUpdateTime())
.append("siteId", getSiteId()) .append("siteId", getSiteId())
.append("authorized", getAuthorized())
.toString(); .toString();
} }
} }

View File

@ -0,0 +1,210 @@
package com.xzzn.ems.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity;
import java.math.BigDecimal;
import java.util.Date;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* 策略运行日志对象 ems_strategy_log
*
* @author xzzn
* @date 2025-12-26
*/
public class EmsStrategyLog extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** $column.columnComment */
private Long id;
/** 策略ID */
@Excel(name = "策略ID")
private Long strategyId;
/** 模版id */
@Excel(name = "模版id")
private String templateId;
/** 站点id */
@Excel(name = "站点id")
private String siteId;
/** 设备唯一标识符 */
@Excel(name = "设备唯一标识符")
private String deviceId;
/** 开始时间 */
@JsonFormat(pattern = "HH:mm")
@Excel(name = "开始时间", width = 30, dateFormat = "HH:mm")
private Date startTime;
/** 结束时间 */
@JsonFormat(pattern = "HH:mm")
@Excel(name = "结束时间", width = 30, dateFormat = "HH:mm")
private Date endTime;
/** 功率 (kW) */
@Excel(name = "功率 (kW)")
private BigDecimal chargeDischargePower;
/** 充电状态如“1-充电”、“2-待机”、“3-放电” */
@Excel(name = "充电状态如“1-充电”、“2-待机”、“3-放电”")
private String chargeStatus;
/** 执行时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "执行时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date executionDate;
/** 防逆流, 1-是、0-否 */
@Excel(name = "防逆流, 1-是、0-否")
private Integer antiReverse;
/** PCS降功率类型 */
@Excel(name = "PCS降功率类型0-降低功率、1-增加功率")
private Integer powerDownType;
public void setId(Long id)
{
this.id = id;
}
public Long getId()
{
return id;
}
public void setStrategyId(Long strategyId)
{
this.strategyId = strategyId;
}
public Long getStrategyId()
{
return strategyId;
}
public void setTemplateId(String templateId)
{
this.templateId = templateId;
}
public String getTemplateId()
{
return templateId;
}
public void setSiteId(String siteId)
{
this.siteId = siteId;
}
public String getSiteId()
{
return siteId;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public void setStartTime(Date startTime)
{
this.startTime = startTime;
}
public Date getStartTime()
{
return startTime;
}
public void setEndTime(Date endTime)
{
this.endTime = endTime;
}
public Date getEndTime()
{
return endTime;
}
public void setChargeDischargePower(BigDecimal chargeDischargePower)
{
this.chargeDischargePower = chargeDischargePower;
}
public BigDecimal getChargeDischargePower()
{
return chargeDischargePower;
}
public void setChargeStatus(String chargeStatus)
{
this.chargeStatus = chargeStatus;
}
public String getChargeStatus()
{
return chargeStatus;
}
public void setExecutionDate(Date executionDate)
{
this.executionDate = executionDate;
}
public Date getExecutionDate()
{
return executionDate;
}
public Integer getAntiReverse() {
return antiReverse;
}
public void setAntiReverse(Integer antiReverse) {
this.antiReverse = antiReverse;
}
public Integer getPowerDownType() {
return powerDownType;
}
public void setPowerDownType(Integer powerDownType) {
this.powerDownType = powerDownType;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("strategyId", getStrategyId())
.append("templateId", getTemplateId())
.append("siteId", getSiteId())
.append("deviceId", getDeviceId())
.append("antiReverse", getAntiReverse())
.append("startTime", getStartTime())
.append("endTime", getEndTime())
.append("chargeDischargePower", getChargeDischargePower())
.append("chargeStatus", getChargeStatus())
.append("executionDate", getExecutionDate())
.append("antiReverse", getAntiReverse())
.append("powerDownType", getPowerDownType())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

View File

@ -0,0 +1,221 @@
package com.xzzn.ems.domain;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.math.BigDecimal;
/**
* 策略运行参数配置对象 ems_strategy_runtime_config
*
* @author xzzn
* @date 2026-02-13
*/
public class EmsStrategyRuntimeConfig extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 主键 */
private Long id;
/** 站点ID */
@Excel(name = "站点ID")
private String siteId;
/** SOC下限(%) */
@Excel(name = "SOC下限(%)")
private BigDecimal socDown;
/** SOC上限(%) */
@Excel(name = "SOC上限(%)")
private BigDecimal socUp;
/** 防逆流阈值(kW) */
@Excel(name = "防逆流阈值(kW)")
private BigDecimal antiReverseThreshold;
/** 防逆流阈值上浮比例(%) */
@Excel(name = "防逆流阈值上浮比例(%)")
private BigDecimal antiReverseRangePercent;
/** 防逆流恢复上限(kW) */
@Excel(name = "防逆流恢复上限(kW)")
private BigDecimal antiReverseUp;
/** 防逆流降功率比例(%) */
@Excel(name = "防逆流降功率比例(%)")
private BigDecimal antiReversePowerDownPercent;
/** 防逆流硬停阈值(kW) */
@Excel(name = "防逆流硬停阈值(kW)")
private BigDecimal antiReverseHardStopThreshold;
/** 设定功率倍率 */
@Excel(name = "设定功率倍率")
private BigDecimal powerSetMultiplier;
/** 保护介入开关1-启用0-禁用) */
@Excel(name = "保护介入开关")
private Integer protectInterveneEnable;
/** 一级保护降额比例(%) */
@Excel(name = "一级保护降额比例(%)")
private BigDecimal protectL1DeratePercent;
/** 保护释放稳定时长(秒) */
@Excel(name = "保护释放稳定时长(秒)")
private Integer protectRecoveryStableSeconds;
/** 三级保护锁存开关1-启用0-禁用) */
@Excel(name = "三级保护锁存开关")
private Integer protectL3LatchEnable;
/** 保护冲突策略 */
@Excel(name = "保护冲突策略")
private String protectConflictPolicy;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public BigDecimal getSocDown() {
return socDown;
}
public void setSocDown(BigDecimal socDown) {
this.socDown = socDown;
}
public BigDecimal getSocUp() {
return socUp;
}
public void setSocUp(BigDecimal socUp) {
this.socUp = socUp;
}
public BigDecimal getAntiReverseThreshold() {
return antiReverseThreshold;
}
public void setAntiReverseThreshold(BigDecimal antiReverseThreshold) {
this.antiReverseThreshold = antiReverseThreshold;
}
public BigDecimal getAntiReverseRangePercent() {
return antiReverseRangePercent;
}
public void setAntiReverseRangePercent(BigDecimal antiReverseRangePercent) {
this.antiReverseRangePercent = antiReverseRangePercent;
}
public BigDecimal getAntiReverseUp() {
return antiReverseUp;
}
public void setAntiReverseUp(BigDecimal antiReverseUp) {
this.antiReverseUp = antiReverseUp;
}
public BigDecimal getAntiReversePowerDownPercent() {
return antiReversePowerDownPercent;
}
public void setAntiReversePowerDownPercent(BigDecimal antiReversePowerDownPercent) {
this.antiReversePowerDownPercent = antiReversePowerDownPercent;
}
public BigDecimal getAntiReverseHardStopThreshold() {
return antiReverseHardStopThreshold;
}
public void setAntiReverseHardStopThreshold(BigDecimal antiReverseHardStopThreshold) {
this.antiReverseHardStopThreshold = antiReverseHardStopThreshold;
}
public BigDecimal getPowerSetMultiplier() {
return powerSetMultiplier;
}
public void setPowerSetMultiplier(BigDecimal powerSetMultiplier) {
this.powerSetMultiplier = powerSetMultiplier;
}
public Integer getProtectInterveneEnable() {
return protectInterveneEnable;
}
public void setProtectInterveneEnable(Integer protectInterveneEnable) {
this.protectInterveneEnable = protectInterveneEnable;
}
public BigDecimal getProtectL1DeratePercent() {
return protectL1DeratePercent;
}
public void setProtectL1DeratePercent(BigDecimal protectL1DeratePercent) {
this.protectL1DeratePercent = protectL1DeratePercent;
}
public Integer getProtectRecoveryStableSeconds() {
return protectRecoveryStableSeconds;
}
public void setProtectRecoveryStableSeconds(Integer protectRecoveryStableSeconds) {
this.protectRecoveryStableSeconds = protectRecoveryStableSeconds;
}
public Integer getProtectL3LatchEnable() {
return protectL3LatchEnable;
}
public void setProtectL3LatchEnable(Integer protectL3LatchEnable) {
this.protectL3LatchEnable = protectL3LatchEnable;
}
public String getProtectConflictPolicy() {
return protectConflictPolicy;
}
public void setProtectConflictPolicy(String protectConflictPolicy) {
this.protectConflictPolicy = protectConflictPolicy;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("siteId", getSiteId())
.append("socDown", getSocDown())
.append("socUp", getSocUp())
.append("antiReverseThreshold", getAntiReverseThreshold())
.append("antiReverseRangePercent", getAntiReverseRangePercent())
.append("antiReverseUp", getAntiReverseUp())
.append("antiReversePowerDownPercent", getAntiReversePowerDownPercent())
.append("antiReverseHardStopThreshold", getAntiReverseHardStopThreshold())
.append("powerSetMultiplier", getPowerSetMultiplier())
.append("protectInterveneEnable", getProtectInterveneEnable())
.append("protectL1DeratePercent", getProtectL1DeratePercent())
.append("protectRecoveryStableSeconds", getProtectRecoveryStableSeconds())
.append("protectL3LatchEnable", getProtectL3LatchEnable())
.append("protectConflictPolicy", getProtectConflictPolicy())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

View File

@ -35,6 +35,14 @@ public class EmsStrategyTempTimeConfig extends BaseEntity
@Excel(name = "充放功率 (kW)") @Excel(name = "充放功率 (kW)")
private BigDecimal chargeDischargePower; private BigDecimal chargeDischargePower;
/** SDC下限 (%) */
@Excel(name = "SDC下限 (%)")
private BigDecimal sdcDown;
/** SDC上限 (%) */
@Excel(name = "SDC上限 (%)")
private BigDecimal sdcUp;
/** 充电状态如“1-充电”、“2-待机” */ /** 充电状态如“1-充电”、“2-待机” */
@Excel(name = "充电状态如“1-充电”、“2-待机”") @Excel(name = "充电状态如“1-充电”、“2-待机”")
private String chargeStatus; private String chargeStatus;
@ -83,6 +91,26 @@ public class EmsStrategyTempTimeConfig extends BaseEntity
return chargeDischargePower; return chargeDischargePower;
} }
public void setSdcDown(BigDecimal sdcDown)
{
this.sdcDown = sdcDown;
}
public BigDecimal getSdcDown()
{
return sdcDown;
}
public void setSdcUp(BigDecimal sdcUp)
{
this.sdcUp = sdcUp;
}
public BigDecimal getSdcUp()
{
return sdcUp;
}
public void setChargeStatus(String chargeStatus) public void setChargeStatus(String chargeStatus)
{ {
this.chargeStatus = chargeStatus; this.chargeStatus = chargeStatus;
@ -110,6 +138,8 @@ public class EmsStrategyTempTimeConfig extends BaseEntity
.append("startTime", getStartTime()) .append("startTime", getStartTime())
.append("endTime", getEndTime()) .append("endTime", getEndTime())
.append("chargeDischargePower", getChargeDischargePower()) .append("chargeDischargePower", getChargeDischargePower())
.append("sdcDown", getSdcDown())
.append("sdcUp", getSdcUp())
.append("chargeStatus", getChargeStatus()) .append("chargeStatus", getChargeStatus())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())

View File

@ -0,0 +1,68 @@
package com.xzzn.ems.domain;
import com.xzzn.common.core.domain.BaseEntity;
public class SysBizRemark extends BaseEntity
{
private static final long serialVersionUID = 1L;
private Long id;
private String bizType;
private String bizKey1;
private String bizKey2;
private String delFlag;
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getBizType()
{
return bizType;
}
public void setBizType(String bizType)
{
this.bizType = bizType;
}
public String getBizKey1()
{
return bizKey1;
}
public void setBizKey1(String bizKey1)
{
this.bizKey1 = bizKey1;
}
public String getBizKey2()
{
return bizKey2;
}
public void setBizKey2(String bizKey2)
{
this.bizKey2 = bizKey2;
}
public String getDelFlag()
{
return delFlag;
}
public void setDelFlag(String delFlag)
{
this.delFlag = delFlag;
}
}

View File

@ -11,6 +11,15 @@ public class AmmeterRevenueStatisListVo {
/** 类别 */ /** 类别 */
private String dataTime; private String dataTime;
/** 是否工作日1-工作日 0-节假日 */
private Integer isWorkday;
/** 日期类型 */
private String dayType;
/** 天气情况 */
private String weatherDesc;
/** 组合有功-总 */ /** 组合有功-总 */
private BigDecimal activeTotalPrice = BigDecimal.ZERO; private BigDecimal activeTotalPrice = BigDecimal.ZERO;
@ -41,6 +50,11 @@ public class AmmeterRevenueStatisListVo {
/** 组合无功-谷 */ /** 组合无功-谷 */
private BigDecimal reActiveValleyPrice = BigDecimal.ZERO; private BigDecimal reActiveValleyPrice = BigDecimal.ZERO;
/** 实际收益 */
private BigDecimal actualRevenue = BigDecimal.ZERO;
private String remark;
public String getDataTime() { public String getDataTime() {
return dataTime; return dataTime;
} }
@ -49,6 +63,30 @@ public class AmmeterRevenueStatisListVo {
this.dataTime = dataTime; this.dataTime = dataTime;
} }
public Integer getIsWorkday() {
return isWorkday;
}
public void setIsWorkday(Integer isWorkday) {
this.isWorkday = isWorkday;
}
public String getDayType() {
return dayType;
}
public void setDayType(String dayType) {
this.dayType = dayType;
}
public String getWeatherDesc() {
return weatherDesc;
}
public void setWeatherDesc(String weatherDesc) {
this.weatherDesc = weatherDesc;
}
public BigDecimal getActiveTotalPrice() { public BigDecimal getActiveTotalPrice() {
return activeTotalPrice; return activeTotalPrice;
} }
@ -128,4 +166,20 @@ public class AmmeterRevenueStatisListVo {
public void setReActiveValleyPrice(BigDecimal reActiveValleyPrice) { public void setReActiveValleyPrice(BigDecimal reActiveValleyPrice) {
this.reActiveValleyPrice = reActiveValleyPrice; this.reActiveValleyPrice = reActiveValleyPrice;
} }
public BigDecimal getActualRevenue() {
return actualRevenue;
}
public void setActualRevenue(BigDecimal actualRevenue) {
this.actualRevenue = actualRevenue;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
} }

View File

@ -46,6 +46,8 @@ public class AmmeterStatisListVo {
/** 效率-有功总/无功总 */ /** 效率-有功总/无功总 */
private BigDecimal effect = BigDecimal.ZERO; private BigDecimal effect = BigDecimal.ZERO;
private String remark;
public String getDataTime() { public String getDataTime() {
return dataTime; return dataTime;
} }
@ -149,4 +151,12 @@ public class AmmeterStatisListVo {
public void setEffect(BigDecimal effect) { public void setEffect(BigDecimal effect) {
this.effect = effect; this.effect = effect;
} }
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
} }

View File

@ -24,6 +24,18 @@ public class BatteryDataStatsListVo {
/** SOH (%) */ /** SOH (%) */
private BigDecimal soh; private BigDecimal soh;
/** 电压映射点位ID */
private String voltagePointId;
/** 温度映射点位ID */
private String temperaturePointId;
/** SOC映射点位ID */
private String socPointId;
/** SOH映射点位ID */
private String sohPointId;
@JsonFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd")
private Date dataTimestamp; private Date dataTimestamp;
@ -71,6 +83,38 @@ public class BatteryDataStatsListVo {
this.soh = soh; this.soh = soh;
} }
public String getVoltagePointId() {
return voltagePointId;
}
public void setVoltagePointId(String voltagePointId) {
this.voltagePointId = voltagePointId;
}
public String getTemperaturePointId() {
return temperaturePointId;
}
public void setTemperaturePointId(String temperaturePointId) {
this.temperaturePointId = temperaturePointId;
}
public String getSocPointId() {
return socPointId;
}
public void setSocPointId(String socPointId) {
this.socPointId = socPointId;
}
public String getSohPointId() {
return sohPointId;
}
public void setSohPointId(String sohPointId) {
this.sohPointId = sohPointId;
}
public Date getDataTimestamp() { public Date getDataTimestamp() {
return dataTimestamp; return dataTimestamp;
} }

View File

@ -0,0 +1,42 @@
package com.xzzn.ems.domain.vo;
import java.util.List;
public class BizRemarkBatchGetRequest
{
private String bizType;
private String bizKey1;
private List<String> bizKey2List;
public String getBizType()
{
return bizType;
}
public void setBizType(String bizType)
{
this.bizType = bizType;
}
public String getBizKey1()
{
return bizKey1;
}
public void setBizKey1(String bizKey1)
{
this.bizKey1 = bizKey1;
}
public List<String> getBizKey2List()
{
return bizKey2List;
}
public void setBizKey2List(List<String> bizKey2List)
{
this.bizKey2List = bizKey2List;
}
}

View File

@ -0,0 +1,52 @@
package com.xzzn.ems.domain.vo;
public class BizRemarkSaveRequest
{
private String bizType;
private String bizKey1;
private String bizKey2;
private String remark;
public String getBizType()
{
return bizType;
}
public void setBizType(String bizType)
{
this.bizType = bizType;
}
public String getBizKey1()
{
return bizKey1;
}
public void setBizKey1(String bizKey1)
{
this.bizKey1 = bizKey1;
}
public String getBizKey2()
{
return bizKey2;
}
public void setBizKey2(String bizKey2)
{
this.bizKey2 = bizKey2;
}
public String getRemark()
{
return remark;
}
public void setRemark(String remark)
{
this.remark = remark;
}
}

View File

@ -24,11 +24,25 @@ public class DevicePointDataList
private BigDecimal avgValue; private BigDecimal avgValue;
// 差值max - min // 差值max - min
private BigDecimal diffValue; private BigDecimal diffValue;
// 第一四分位数
private BigDecimal q1;
// 中位数
private BigDecimal median;
// 第三四分位数
private BigDecimal q3;
public DevicePointDataList(String deviceId, List<GeneralQueryDataVo> pointValueList,String parentDeviceId, public DevicePointDataList(String deviceId, List<GeneralQueryDataVo> pointValueList,String parentDeviceId,
BigDecimal maxValue, BigDecimal minValue, BigDecimal maxValue, BigDecimal minValue,
BigDecimal avgValue, BigDecimal diffValue, BigDecimal avgValue, BigDecimal diffValue,
String maxDate, String minDate) { String maxDate, String minDate) {
this(deviceId, pointValueList, parentDeviceId, maxValue, minValue, avgValue, diffValue, maxDate, minDate, null, null, null);
}
public DevicePointDataList(String deviceId, List<GeneralQueryDataVo> pointValueList,String parentDeviceId,
BigDecimal maxValue, BigDecimal minValue,
BigDecimal avgValue, BigDecimal diffValue,
String maxDate, String minDate,
BigDecimal q1, BigDecimal median, BigDecimal q3) {
this.deviceId = deviceId; this.deviceId = deviceId;
this.pointValueList = pointValueList; this.pointValueList = pointValueList;
this.parentDeviceId = parentDeviceId; this.parentDeviceId = parentDeviceId;
@ -38,6 +52,9 @@ public class DevicePointDataList
this.diffValue = diffValue; this.diffValue = diffValue;
this.maxDate = maxDate; this.maxDate = maxDate;
this.minDate = minDate; this.minDate = minDate;
this.q1 = q1;
this.median = median;
this.q3 = q3;
} }
public DevicePointDataList(String deviceId, String parentDeviceId, List<GeneralQueryDataVo> pointValueList) { public DevicePointDataList(String deviceId, String parentDeviceId, List<GeneralQueryDataVo> pointValueList) {
@ -121,4 +138,28 @@ public class DevicePointDataList
public void setDiffValue(BigDecimal diffValue) { public void setDiffValue(BigDecimal diffValue) {
this.diffValue = diffValue; this.diffValue = diffValue;
} }
public BigDecimal getQ1() {
return q1;
}
public void setQ1(BigDecimal q1) {
this.q1 = q1;
}
public BigDecimal getMedian() {
return median;
}
public void setMedian(BigDecimal median) {
this.median = median;
}
public BigDecimal getQ3() {
return q3;
}
public void setQ3(BigDecimal q3) {
this.q3 = q3;
}
} }

View File

@ -1,7 +1,5 @@
package com.xzzn.ems.domain.vo; package com.xzzn.ems.domain.vo;
import java.util.List;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
/** /**
@ -18,16 +16,13 @@ public class DeviceUpdateRequest {
@NotBlank(message = "设备ID不能为空") @NotBlank(message = "设备ID不能为空")
private String deviceId; private String deviceId;
/** 设备状态0-离线、1-待机、2-运行、3-故障、4-停机 */ /** 工作状态0-运行 1-停机 2-故障 */
private String deviceStatus; @NotBlank(message = "工作状态状态不能为空")
private String workStatus;
/** 设备类型 */ /** 设备类型 */
private String deviceCategory; private String deviceCategory;
/** 设备点位数据匹配表字段 */
private List<String> matchFields;
public String getSiteId() { public String getSiteId() {
return siteId; return siteId;
} }
@ -44,12 +39,12 @@ public class DeviceUpdateRequest {
this.deviceId = deviceId; this.deviceId = deviceId;
} }
public String getDeviceStatus() { public String getWorkStatus() {
return deviceStatus; return workStatus;
} }
public void setDeviceStatus(String deviceStatus) { public void setWorkStatus(String workStatus) {
this.deviceStatus = deviceStatus; this.workStatus = workStatus;
} }
public String getDeviceCategory() { public String getDeviceCategory() {
@ -60,11 +55,4 @@ public class DeviceUpdateRequest {
this.deviceCategory = deviceCategory; this.deviceCategory = deviceCategory;
} }
public List<String> getMatchFields() {
return matchFields;
}
public void setMatchFields(List<String> matchFields) {
this.matchFields = matchFields;
}
} }

View File

@ -0,0 +1,20 @@
package com.xzzn.ems.domain.vo;
import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsPcsSetting;
public class DevicesSettingVo extends EmsDevicesSetting
{
private static final long serialVersionUID = 1L;
/** PCS设备配置 */
private EmsPcsSetting pcsSetting;
public EmsPcsSetting getPcsSetting() {
return pcsSetting;
}
public void setPcsSetting(EmsPcsSetting pcsSetting) {
this.pcsSetting = pcsSetting;
}
}

View File

@ -1,7 +1,6 @@
package com.xzzn.ems.domain.vo; package com.xzzn.ems.domain.vo;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date;
/** /**
* 实时运行-有功无功功率数据 * 实时运行-有功无功功率数据
@ -31,6 +30,8 @@ public class EnergyStoragePowVo {
private String deviceId; private String deviceId;
private String groupTime;
public String getDeviceId() { public String getDeviceId() {
return deviceId; return deviceId;
} }
@ -67,6 +68,14 @@ public class EnergyStoragePowVo {
this.dateDay = dateDay; this.dateDay = dateDay;
} }
public String getGroupTime() {
return groupTime;
}
public void setGroupTime(String groupTime) {
this.groupTime = groupTime;
}
public void setPcsTotalReactivePower(BigDecimal pcsTotalReactivePower) { public void setPcsTotalReactivePower(BigDecimal pcsTotalReactivePower) {
this.pcsTotalReactivePower = pcsTotalReactivePower; this.pcsTotalReactivePower = pcsTotalReactivePower;
} }

View File

@ -0,0 +1,40 @@
package com.xzzn.ems.domain.vo;
public class GeneralQueryPointOptionVo {
private String pointId;
private String pointName;
private String dataKey;
private String pointDesc;
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getPointName() {
return pointName;
}
public void setPointName(String pointName) {
this.pointName = pointName;
}
public String getDataKey() {
return dataKey;
}
public void setDataKey(String dataKey) {
this.dataKey = dataKey;
}
public String getPointDesc() {
return pointDesc;
}
public void setPointDesc(String pointDesc) {
this.pointDesc = pointDesc;
}
}

View File

@ -0,0 +1,32 @@
package com.xzzn.ems.domain.vo;
import javax.validation.constraints.NotBlank;
/**
* 按站点导入点位模板请求参数
*/
public class ImportPointTemplateRequest {
/** 目标站点ID */
@NotBlank(message = "站点ID不能为空")
private String siteId;
/** 是否覆盖目标站点已有点位配置 */
private Boolean overwrite;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public Boolean getOverwrite() {
return overwrite;
}
public void setOverwrite(Boolean overwrite) {
this.overwrite = overwrite;
}
}

View File

@ -0,0 +1,76 @@
package com.xzzn.ems.domain.vo;
public class PointConfigCurveRequest {
private String siteId;
private String deviceId;
private String pointId;
private String dataKey;
private String pointType;
private String rangeType;
private String startTime;
private String endTime;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getDataKey() {
return dataKey;
}
public void setDataKey(String dataKey) {
this.dataKey = dataKey;
}
public String getPointType() {
return pointType;
}
public void setPointType(String pointType) {
this.pointType = pointType;
}
public String getRangeType() {
return rangeType;
}
public void setRangeType(String rangeType) {
this.rangeType = rangeType;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
}

View File

@ -0,0 +1,24 @@
package com.xzzn.ems.domain.vo;
import java.util.Date;
public class PointConfigCurveValueVo {
private Date dataTime;
private Object pointValue;
public Date getDataTime() {
return dataTime;
}
public void setDataTime(Date dataTime) {
this.dataTime = dataTime;
}
public Object getPointValue() {
return pointValue;
}
public void setPointValue(Object pointValue) {
this.pointValue = pointValue;
}
}

View File

@ -0,0 +1,38 @@
package com.xzzn.ems.domain.vo;
import javax.validation.constraints.NotBlank;
public class PointConfigGenerateRecentRequest {
@NotBlank(message = "站点ID不能为空")
private String siteId;
@NotBlank(message = "点位ID不能为空")
private String pointId;
private String deviceId;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
}

View File

@ -0,0 +1,40 @@
package com.xzzn.ems.domain.vo;
public class PointConfigLatestValueItemVo {
private String siteId;
private String pointId;
private String deviceId;
private String dataKey;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getDataKey() {
return dataKey;
}
public void setDataKey(String dataKey) {
this.dataKey = dataKey;
}
}

View File

@ -0,0 +1,15 @@
package com.xzzn.ems.domain.vo;
import java.util.List;
public class PointConfigLatestValueRequest {
private List<PointConfigLatestValueItemVo> points;
public List<PointConfigLatestValueItemVo> getPoints() {
return points;
}
public void setPoints(List<PointConfigLatestValueItemVo> points) {
this.points = points;
}
}

View File

@ -0,0 +1,60 @@
package com.xzzn.ems.domain.vo;
import java.util.Date;
public class PointConfigLatestValueVo {
private String siteId;
private String pointId;
private String deviceId;
private String dataKey;
private Object pointValue;
private Date dataTime;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getDataKey() {
return dataKey;
}
public void setDataKey(String dataKey) {
this.dataKey = dataKey;
}
public Object getPointValue() {
return pointValue;
}
public void setPointValue(Object pointValue) {
this.pointValue = pointValue;
}
public Date getDataTime() {
return dataTime;
}
public void setDataTime(Date dataTime) {
this.dataTime = dataTime;
}
}

View File

@ -1,9 +1,10 @@
package com.xzzn.ems.domain.vo; package com.xzzn.ems.domain.vo;
import javax.validation.constraints.NotBlank;
import java.math.BigDecimal; import java.math.BigDecimal;
import javax.validation.constraints.NotBlank;
/** /**
* 设备列表-点位详情-入参 * 设备列表-点位详情-入参
* *
@ -24,6 +25,8 @@ public class PointDataRequest {
private String dataPointName; private String dataPointName;
/** 点位-模糊查询 */ /** 点位-模糊查询 */
private String dataPoint; private String dataPoint;
/** 点位匹配字段 */
private String matchField;
/** 点位数据-范围上下限 */ /** 点位数据-范围上下限 */
private BigDecimal lower; private BigDecimal lower;
private BigDecimal upper; private BigDecimal upper;
@ -126,6 +129,14 @@ public class PointDataRequest {
this.dataPoint = dataPoint; this.dataPoint = dataPoint;
} }
public String getMatchField() {
return matchField;
}
public void setMatchField(String matchField) {
this.matchField = matchField;
}
public BigDecimal getLower() { public BigDecimal getLower() {
return lower; return lower;
} }

View File

@ -14,6 +14,9 @@ public class PointNameRequest {
private String deviceCategory; private String deviceCategory;
private String pointName; private String pointName;
private List<String> pointNames;
private String pointId;
private List<String> pointIds;
/** 数据分组 1-分钟 2-小时 3-天 */ /** 数据分组 1-分钟 2-小时 3-天 */
private int dataUnit; private int dataUnit;
@ -50,6 +53,30 @@ public class PointNameRequest {
this.pointName = pointName; this.pointName = pointName;
} }
public List<String> getPointNames() {
return pointNames;
}
public void setPointNames(List<String> pointNames) {
this.pointNames = pointNames;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public List<String> getPointIds() {
return pointIds;
}
public void setPointIds(List<String> pointIds) {
this.pointIds = pointIds;
}
public int getDataUnit() { public int getDataUnit() {
return dataUnit; return dataUnit;
} }

View File

@ -14,6 +14,10 @@ public class PointQueryResponse
@Excel(name = "点位名称") @Excel(name = "点位名称")
private String pointName; private String pointName;
/** 点位匹配字段 */
@Excel(name = "点位匹配字段")
private String matchField;
/** 数据点位 */ /** 数据点位 */
@Excel(name = "数据点位") @Excel(name = "数据点位")
private String dataPoint; private String dataPoint;
@ -110,6 +114,14 @@ public class PointQueryResponse
this.pointName = pointName; this.pointName = pointName;
} }
public String getMatchField() {
return matchField;
}
public void setMatchField(String matchField) {
this.matchField = matchField;
}
public String getDataUnit() { public String getDataUnit() {
return dataUnit; return dataUnit;
} }

View File

@ -31,6 +31,8 @@ public class PowerStatisListVo {
*/ */
private BigDecimal pvPower; private BigDecimal pvPower;
public PowerStatisListVo() {}
public PowerStatisListVo(String statisDate, BigDecimal gridPower, BigDecimal storagePower, BigDecimal pvPower) { public PowerStatisListVo(String statisDate, BigDecimal gridPower, BigDecimal storagePower, BigDecimal pvPower) {
this.statisDate = statisDate; this.statisDate = statisDate;
this.gridPower = gridPower; this.gridPower = gridPower;

View File

@ -0,0 +1,114 @@
package com.xzzn.ems.domain.vo;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* 站点保护约束(供策略轮询仲裁)
*/
public class ProtectionConstraintVo implements Serializable {
private static final long serialVersionUID = 1L;
/** 最高保护等级0-无保护1/2/3 对应故障等级 */
private Integer level;
/** 是否允许充电 */
private Boolean allowCharge;
/** 是否允许放电 */
private Boolean allowDischarge;
/** 功率上限比例0~1 */
private BigDecimal powerLimitRatio;
/** 是否强制待机 */
private Boolean forceStandby;
/** 是否强制停机 */
private Boolean forceStop;
/** 生效的方案ID列表 */
private List<Long> sourcePlanIds;
/** 更新时间戳(毫秒) */
private Long updateAt;
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
public Boolean getAllowCharge() {
return allowCharge;
}
public void setAllowCharge(Boolean allowCharge) {
this.allowCharge = allowCharge;
}
public Boolean getAllowDischarge() {
return allowDischarge;
}
public void setAllowDischarge(Boolean allowDischarge) {
this.allowDischarge = allowDischarge;
}
public BigDecimal getPowerLimitRatio() {
return powerLimitRatio;
}
public void setPowerLimitRatio(BigDecimal powerLimitRatio) {
this.powerLimitRatio = powerLimitRatio;
}
public Boolean getForceStandby() {
return forceStandby;
}
public void setForceStandby(Boolean forceStandby) {
this.forceStandby = forceStandby;
}
public Boolean getForceStop() {
return forceStop;
}
public void setForceStop(Boolean forceStop) {
this.forceStop = forceStop;
}
public List<Long> getSourcePlanIds() {
return sourcePlanIds;
}
public void setSourcePlanIds(List<Long> sourcePlanIds) {
this.sourcePlanIds = sourcePlanIds;
}
public Long getUpdateAt() {
return updateAt;
}
public void setUpdateAt(Long updateAt) {
this.updateAt = updateAt;
}
public static ProtectionConstraintVo empty() {
ProtectionConstraintVo vo = new ProtectionConstraintVo();
vo.setLevel(0);
vo.setAllowCharge(true);
vo.setAllowDischarge(true);
vo.setPowerLimitRatio(BigDecimal.ONE);
vo.setForceStandby(false);
vo.setForceStop(false);
vo.setSourcePlanIds(new ArrayList<>());
vo.setUpdateAt(System.currentTimeMillis());
return vo;
}
}

View File

@ -9,6 +9,9 @@ public class ProtectionSettingVo {
/** 设备 */ /** 设备 */
private String deviceId; private String deviceId;
/** 设备名称 */
private String deviceName;
/** 点位 */ /** 点位 */
private String point; private String point;
@ -44,6 +47,14 @@ public class ProtectionSettingVo {
this.deviceId = deviceId; this.deviceId = deviceId;
} }
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getPoint() { public String getPoint() {
return point; return point;
} }

View File

@ -0,0 +1,38 @@
package com.xzzn.ems.domain.vo;
import java.util.ArrayList;
import java.util.List;
/**
* 告警保护方案-保护前提分组
*/
public class ProtectionSettingsGroupVo {
/** 故障保护前提 */
private List<ProtectionSettingVo> faultSettings;
/** 释放保护前提 */
private List<ProtectionSettingVo> releaseSettings;
public static ProtectionSettingsGroupVo empty() {
ProtectionSettingsGroupVo vo = new ProtectionSettingsGroupVo();
vo.setFaultSettings(new ArrayList<>());
vo.setReleaseSettings(new ArrayList<>());
return vo;
}
public List<ProtectionSettingVo> getFaultSettings() {
return faultSettings;
}
public void setFaultSettings(List<ProtectionSettingVo> faultSettings) {
this.faultSettings = faultSettings;
}
public List<ProtectionSettingVo> getReleaseSettings() {
return releaseSettings;
}
public void setReleaseSettings(List<ProtectionSettingVo> releaseSettings) {
this.releaseSettings = releaseSettings;
}
}

View File

@ -1,6 +1,7 @@
package com.xzzn.ems.domain.vo; package com.xzzn.ems.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date; import java.util.Date;
@ -10,10 +11,12 @@ import java.util.Date;
public class RunningGraphRequest { public class RunningGraphRequest {
/** 开始时间 */ /** 开始时间 */
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd")
private Date startDate; private Date startDate;
/** 结束时间 */ /** 结束时间 */
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate; private Date endDate;

View File

@ -0,0 +1,41 @@
package com.xzzn.ems.domain.vo;
public class SingleBatteryConfigInitRequest {
private String siteId;
private String scopeType;
private String scopeDeviceId;
private Integer targetCount;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getScopeType() {
return scopeType;
}
public void setScopeType(String scopeType) {
this.scopeType = scopeType;
}
public String getScopeDeviceId() {
return scopeDeviceId;
}
public void setScopeDeviceId(String scopeDeviceId) {
this.scopeDeviceId = scopeDeviceId;
}
public Integer getTargetCount() {
return targetCount;
}
public void setTargetCount(Integer targetCount) {
this.targetCount = targetCount;
}
}

Some files were not shown because too many files have changed in this diff Show More