Compare commits

...

60 Commits

Author SHA1 Message Date
056e06b00a Merge pull request 'dev' (#4) from dev into main
Reviewed-on: #4
2026-04-09 01:32:12 +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
161 changed files with 16406 additions and 4773 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

@ -13,7 +13,9 @@ import com.xzzn.ems.domain.vo.DeviceUpdateRequest;
import com.xzzn.ems.domain.vo.DevicesSettingVo; 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.SiteMonitorProjectPointMappingSaveRequest;
import com.xzzn.ems.domain.vo.SiteDeviceListVo; import com.xzzn.ems.domain.vo.SiteDeviceListVo;
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;
@ -59,6 +61,26 @@ public class EmsSiteConfigController extends BaseController{
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));
}
/** /**
* 获取设备列表-分页 * 获取设备列表-分页
*/ */
@ -67,7 +89,8 @@ public class EmsSiteConfigController extends BaseController{
@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);
} }
@ -191,6 +214,44 @@ public class EmsSiteConfigController extends BaseController{
return success(iEmsDeviceSettingService.getDeviceListBySiteAndCategory(siteId, deviceCategory)); return success(iEmsDeviceSettingService.getDeviceListBySiteAndCategory(siteId, deviceCategory));
} }
/**
* 获取单站监控项目点位映射
*/
@GetMapping("/getSingleMonitorProjectPointMapping")
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);
}
/**
* 获取单站监控工作状态枚举映射PCS
*/
@GetMapping("/getSingleMonitorWorkStatusEnumMappings")
public AjaxResult getSingleMonitorWorkStatusEnumMappings(@RequestParam String siteId)
{
return success(iEmsDeviceSettingService.getSiteWorkStatusEnumMappings(siteId));
}
/**
* 保存单站监控工作状态枚举映射PCS
*/
@PostMapping("/saveSingleMonitorWorkStatusEnumMappings")
public AjaxResult saveSingleMonitorWorkStatusEnumMappings(@RequestBody WorkStatusEnumMappingSaveRequest request)
{
int rows = iEmsDeviceSettingService.saveSiteWorkStatusEnumMappings(request.getSiteId(), request.getMappings(), getUsername());
return AjaxResult.success(rows);
}
/** /**
* PCS设备开关机 * PCS设备开关机
*/ */

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,12 +6,7 @@ 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.IDDSDataProcessService;
import com.xzzn.ems.service.IDeviceDataProcessService; import com.xzzn.ems.service.IDeviceDataProcessService;
import com.xzzn.ems.service.IEmsMqttMessageService;
import com.xzzn.ems.service.IEmsStrategyService;
import com.xzzn.ems.service.IFXXAlarmDataProcessService;
import com.xzzn.ems.service.IFXXDataProcessService;
import com.xzzn.ems.service.IMqttSyncLogService; 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;
@ -27,6 +22,7 @@ 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;
@ -38,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) {
@ -136,51 +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) {
e.printStackTrace();
log.error("Failed to process device alarm data message: " + e.getMessage(), e);
}
} }
@ -191,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
@ -292,4 +248,4 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber {
} }
} }
} }

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,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

@ -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

@ -103,6 +103,9 @@ public class RedisKeyConstants
/** 设备信息初始化 */ /** 设备信息初始化 */
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";
@ -122,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

@ -28,11 +28,13 @@ 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;
@ -42,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;
@ -51,18 +61,51 @@ public class ModbusProcessor {
public boolean writeDataToDevice(DeviceConfig config) { public boolean writeDataToDevice(DeviceConfig config) {
logger.info("writeDataToDevice: {}", JSON.toJSONString(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);
} }
@ -70,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));
@ -79,7 +122,8 @@ 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: {
@ -89,7 +133,8 @@ public class ModbusProcessor {
if (doubleValue < -32768 || doubleValue > 32767) { if (doubleValue < -32768 || doubleValue > 32767) {
logger.warn("Value {} out of range for 16-bit signed register at address {}", doubleValue, address); logger.warn("Value {} out of range for 16-bit signed register at address {}", doubleValue, address);
} }
writeRegisterRequest(master, config.getSlaveId(), address, (int) doubleValue); boolean result = writeRegisterRequest(master, config.getSlaveId(), address, (int) doubleValue);
tag.setWrite(result);
break; break;
} }
default: default:
@ -97,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);
@ -107,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) {
@ -127,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);
@ -135,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) {
@ -181,18 +236,16 @@ public class ModbusProcessor {
public ModbusMaster borrowMaster(DeviceConfig config) throws Exception { public ModbusMaster borrowMaster(DeviceConfig config) throws Exception {
ModbusMaster master = connectionManager.borrowMaster(config); ModbusMaster master = connectionManager.borrowMaster(config);
// 设置了Modbus通信的超时时间为3000毫秒3秒)。当主设备与从设备通信时,若在3秒内未收到响应,则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。 // 设置了Modbus通信的超时时间为5000毫秒5秒)。当主设备与从设备通信时,若在5秒内未收到响应,则认为通信超时并抛出异常。这有助于避免长时间等待无响应的设备。
master.setTimeout(10000); master.setTimeout(readTimeout);
master.setRetries(readRetries);
return master; 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")){
@ -202,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 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;
} }
@ -299,77 +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);
List<String> logInfoList = new ArrayList<>();
for (TagConfig tag : tags){
StringBuilder logInfo = new StringBuilder();
logInfo.append(tag.getAddress());
if (tag.getBit()!=null){
// logger.info("批处理读取寄存器成功: {}",tag.getAddress() +"(" + tag.getBit() + "):" + results.getValue(tag.getKey()));
logInfo.append("(" + tag.getBit() + "):");
}else {
// logger.info("批处理读取寄存器成功: {}",tag.getAddress() + ":" + results.getValue(tag.getKey()));
logInfo.append(":");
}
logInfo.append(results.getValue(tag.getKey()));
logInfoList.add(logInfo.toString());
}
logger.info("批处理读取寄存器成功: {}", JSON.toJSONString(logInfoList));
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

@ -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

@ -416,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

@ -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,39 +2,38 @@ 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.enums.DeviceRunningStatus; import com.xzzn.common.enums.DeviceRunningStatus;
import com.xzzn.common.utils.DateUtils; import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsPointConfig;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper; 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 com.xzzn.quartz.domain.SysJob;
import com.xzzn.quartz.service.ISysJobService;
import com.xzzn.quartz.util.CronUtils;
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.Objects;
import java.util.concurrent.ConcurrentHashMap; 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;
@ -49,105 +48,225 @@ import org.springframework.util.CollectionUtils;
@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 ScheduledTask scheduledTask;
private final ObjectMapper objectMapper = new ObjectMapper();
private final Map<String, Integer> deviceFailureCounts = new ConcurrentHashMap<>(); private final Map<String, Integer> deviceFailureCounts = new ConcurrentHashMap<>();
private final AtomicBoolean polling = new AtomicBoolean(false);
@Resource(name = "modbusExecutor")
private ExecutorService modbusExecutor;
@Autowired @Autowired
private ModbusProcessor modbusProcessor; private ModbusProcessor modbusProcessor;
@Autowired @Autowired
private IEmsAlarmRecordsService iEmsAlarmRecordsService; private IEmsAlarmRecordsService iEmsAlarmRecordsService;
@Autowired
private ISysJobService iSysJobService;
@Autowired @Autowired
private DeviceDataProcessServiceImpl deviceDataProcessServiceImpl; private DeviceDataProcessServiceImpl deviceDataProcessServiceImpl;
@Autowired @Autowired
private EmsDevicesSettingMapper emsDevicesSettingMapper; private EmsDevicesSettingMapper emsDevicesSettingMapper;
@Autowired @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);
}
}
int interval = getScheduledTaskInterval();
// 为每个 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); 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);
}
});
try {
future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Modbus轮询任务等待中断");
} catch (Exception e) {
log.error("Modbus轮询任务执行异常", e);
} finally {
polling.set(false);
} }
} }
public void scheduledStart(DeviceConfig config) { private List<PollingTask> buildPollingTasks() {
List<EmsPointConfig> pointConfigs = emsPointConfigMapper.selectModbusCollectPointConfigs(null);
if (CollectionUtils.isEmpty(pointConfigs)) {
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()) { if (config.isEnabled()) {
log.info("Reading data from devices: {}", config.getDeviceName()); log.info("Reading data from devices: {}", config.getDeviceName());
ModbusMaster master = null;
try { // 带重试的读取最多重试2次
master = modbusProcessor.borrowMaster(config); Map<String, Object> data = readWithRetry(config, 2);
} catch (Exception e) {
log.error("Failed to borrow connection '{}'", config.getDeviceName(), e);
// 处理设备连接失败的情况,更新设备状态为离线,添加报警记录
addDeviceOfflineRecord(siteId, config.getDeviceNumber());
return;
}
List<String> rawValuEmptyList = new ArrayList<>(); List<String> rawValuEmptyList = new ArrayList<>();
Map<String, Object> data = modbusProcessor.readDataFromDevice(config, master);
// 在这里处理采集到的数据空 // 在这里处理采集到的数据空
config.getTags().forEach(tag -> { config.getTags().forEach(tag -> {
Object rawValue = data.get(tag.getKey()); Object rawValue = data.get(tag.getKey());
@ -180,29 +299,76 @@ public class ModbusPoller {
log.info("Data from {}: {}", config.getDeviceName(), data); log.info("Data from {}: {}", config.getDeviceName(), data);
String deviceNumber = config.getDeviceNumber(); String deviceNumber = config.getDeviceNumber();
//处理数据并发送MQTT消息、保存Redis数据和数据入库 //处理数据并发送MQTT消息、保存Redis数据和数据入库
processingData(data, deviceNumber); processingData(siteId, data, deviceNumber);
} }
} }
private void processingData(Map<String, Object> data, String 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)) { if (CollectionUtils.isEmpty(data)) {
// 增加失败计数 // 增加失败计数
int failureCount = deviceFailureCounts.getOrDefault(deviceNumber, 0) + 1; int failureCount = deviceFailureCounts.getOrDefault(siteDeviceKey, 0) + 1;
deviceFailureCounts.put(deviceNumber, failureCount); deviceFailureCounts.put(siteDeviceKey, failureCount);
log.warn("设备 {} 数据读取失败,当前连续失败次数: {}", deviceNumber, failureCount); log.warn("设备 {} 数据读取失败,当前连续失败次数: {}", siteDeviceKey, failureCount);
// 连续6次失败触发报警 // 连续6次失败触发报警
if (failureCount >= 6) { if (failureCount >= SITE_DEVICE_OFFLINE_THRESHOLD) {
addDeviceOfflineRecord(siteId, deviceNumber); addDeviceOfflineRecord(siteId, deviceNumber);
log.error("设备 {} 连续 {} 次未读取到数据,触发报警", deviceNumber, failureCount); log.error("设备 {} 连续 {} 次未读取到数据,触发报警", siteDeviceKey, failureCount);
} }
return; return;
} }
// 数据读取成功,重置计数器 // 数据读取成功,重置计数器
deviceFailureCounts.remove(deviceNumber); deviceFailureCounts.remove(siteDeviceKey);
updateDeviceStatus(siteId, deviceNumber, DeviceRunningStatus.ONLINE.getCode()); // 读取到数据后告警自恢复
deleteDeviceOfflineRecord(siteId, deviceNumber);
// 发送MQTT消息、保存Redis数据和数据入库 // 发送MQTT消息、保存Redis数据和数据入库
Long timestamp = System.currentTimeMillis(); Long timestamp = System.currentTimeMillis();
@ -210,9 +376,42 @@ public class ModbusPoller {
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) {
@ -225,20 +424,24 @@ public class ModbusPoller {
} }
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);
} }
//处理设备连接失败的情况,更新设备状态为离线,添加报警记录 //处理设备连接失败的情况,更新设备状态为离线,添加报警记录
@ -247,6 +450,12 @@ public class ModbusPoller {
iEmsAlarmRecordsService.addDeviceOfflineRecord(siteId, deviceNumber); 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) { private void updateDeviceStatus(String siteId, String deviceNumber, String deviceStatus) {
EmsDevicesSetting emsDevicesSetting = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceNumber, siteId); EmsDevicesSetting emsDevicesSetting = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceNumber, siteId);
@ -256,11 +465,22 @@ public class ModbusPoller {
} }
} }
private int getScheduledTaskInterval() { private static class PollingTask {
SysJob query = new SysJob(); private final String siteId;
query.setInvokeTarget("modbusPoller.pollAllDevices"); private final DeviceConfig deviceConfig;
List<SysJob> sysJobs = iSysJobService.selectJobList(query);
return Math.toIntExact(CronUtils.getNextExecutionIntervalMillis(sysJobs.get(0).getCronExpression())); 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,40 +10,16 @@ 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.EmsAlarmRecords; import com.xzzn.ems.domain.EmsAlarmRecords;
import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsFaultIssueLog;
import com.xzzn.ems.domain.EmsFaultProtectionPlan; import com.xzzn.ems.domain.EmsFaultProtectionPlan;
import com.xzzn.ems.domain.EmsStrategyRunning; 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.domain.vo.ProtectionSettingsGroupVo;
import com.xzzn.ems.mapper.EmsAlarmRecordsMapper; import com.xzzn.ems.mapper.EmsAlarmRecordsMapper;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsFaultIssueLogMapper;
import com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper; import com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.service.IEmsFaultProtectionPlanService; import com.xzzn.ems.service.IEmsFaultProtectionPlanService;
import com.xzzn.framework.manager.ModbusConnectionManager;
import com.xzzn.framework.manager.ModbusConnectionWrapper;
import com.xzzn.framework.web.service.ModbusService;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -51,14 +27,29 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
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.TimeUnit;
import java.util.regex.Matcher;
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
@ -66,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;
@ -88,297 +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);
EmsFaultProtectionPlan emsFaultProtectionPlan = new EmsFaultProtectionPlan();
emsFaultProtectionPlan.setSiteId("021_DDS_01");
List<EmsFaultProtectionPlan> planList = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(emsFaultProtectionPlan);
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) {
logger.info("<轮询保护方案> 站点:{}方案ID:{}", plan.getSiteId(), plan.getId()); logger.info("<轮询保护方案> 站点:{}方案ID:{}", plan.getSiteId(), plan.getId());
boolean isHighLevel = false;
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();
Long status = plan.getStatus();
if (status == null) {
status = ProtPlanStatus.STOP.getCode();
}
if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) { if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) {
logger.info("<方案未启用> 站点:{}方案ID:{}", siteId, plan.getId()); if (checkIsNeedIssuedPlan(faultSettings, siteId)) {
// 未启用,获取方案的故障值与最新数据判断是否需要下发方案 int faultDelay = safeDelaySeconds(plan.getFaultDelaySeconds(), 0);
if(checkIsNeedIssuedPlan(protSettings, siteId)){ scheduledExecutorService.schedule(() -> {
if("3".equals(plan.getFaultLevel())){ if (!checkIsNeedIssuedPlan(faultSettings, siteId)) {
isHighLevel = true;//最高故障等级 return;
}
// 延时
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连接设备下发方案
String protPlanJson = plan.getProtectionPlan();
if (protPlanJson != null && !protPlanJson.isEmpty()) {
logger.info("<下发保护方案> 方案内容:{}", protPlanJson);
executeProtectionActions(protPlanJson,siteId,plan.getId(),plan.getFaultLevel()); // 执行Modbus指令
}
// 更新方案状态为“已启用”
logger.info("<方案已启用> 方案ID:{}", plan.getId());
plan.setStatus(ProtPlanStatus.RUNNING.getCode());
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
// 更新该站点策略为暂停状态
updateStrategyRunningStatus(siteId, StrategyStatus.SUSPENDED.getCode());
} }
}, faultDelay, TimeUnit.SECONDS); if (Integer.valueOf(1).equals(isAlertAlarm)) {
} EmsAlarmRecords alarmRecords = addAlarmRecord(siteId, plan.getFaultName(), getAlarmLevel(plan.getFaultLevel()));
} else { emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords);
logger.info("<方案已启用> 站点:{}方案ID:{}", siteId, plan.getId());
// 已启用,则获取方案的释放值与最新数据判断是否需要取消方案
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);
updateStrategyRunningStatus(siteId, StrategyStatus.RUNNING.getCode()); }, 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);
logger.info("checkIsNeedCancelPlan 点位:{},最新值:{},比较方式:{},释放值:{}", point, lastPointValue, vo.getReleaseOperator(), releaseValue); 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 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);
logger.info("checkIsNeedIssuedPlan 点位:{},最新值:{},比较方式:{},故障值:{}", point, lastPointValue, vo.getFaultOperator(), faultValue); 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 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 updateStrategyRunningStatus(String siteId, String status) {
// 获取是否有正在运行的策略,如果有则不更改
EmsStrategyRunning query = new EmsStrategyRunning();
query.setSiteId(siteId);
query.setStatus(StrategyStatus.RUNNING.getCode().equals(status) ? StrategyStatus.SUSPENDED.getCode() : StrategyStatus.RUNNING.getCode());
List<EmsStrategyRunning> strategyRunningList = emsStrategyRunningMapper.selectEmsStrategyRunningList(query);
if (CollectionUtils.isNotEmpty(strategyRunningList)) {
// 获取已存在并且状态为:未启用和已暂停的最晚一条策略,更新为已启用
strategyRunningList.forEach(emsStrategyRunning -> {
emsStrategyRunning.setStatus(status);
emsStrategyRunningMapper.updateEmsStrategyRunning(emsStrategyRunning);
});
}
}
// 更新站点策略为启用
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);
@ -391,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);
@ -421,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) {
@ -433,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 ">":
@ -462,11 +601,10 @@ 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);
boolean nextResult = atomicResults.get(i+1); boolean nextResult = atomicResults.get(i + 1);
if ("&&".equals(relation)) { if ("&&".equals(relation)) {
finalResult = finalResult && nextResult; finalResult = finalResult && nextResult;
} else if ("||".equals(relation)) { } else if ("||".equals(relation)) {

View File

@ -2,11 +2,16 @@ package com.xzzn.quartz.task;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
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.WriteTagConfig; 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.ChargeStatus;
import com.xzzn.common.enums.DeviceCategory; import com.xzzn.common.enums.DeviceCategory;
import com.xzzn.common.enums.OperatorType;
import com.xzzn.common.enums.SiteDevice; import com.xzzn.common.enums.SiteDevice;
import com.xzzn.common.enums.SocLimit; import com.xzzn.common.enums.SocLimit;
import com.xzzn.common.enums.WorkStatus; import com.xzzn.common.enums.WorkStatus;
@ -16,31 +21,40 @@ import com.xzzn.ems.domain.EmsAmmeterData;
import com.xzzn.ems.domain.EmsBatteryStack; import com.xzzn.ems.domain.EmsBatteryStack;
import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsPcsSetting; import com.xzzn.ems.domain.EmsPcsSetting;
import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
import com.xzzn.ems.domain.EmsStrategyLog; 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.ProtectionConstraintVo;
import com.xzzn.ems.domain.vo.StrategyRunningVo; import com.xzzn.ems.domain.vo.StrategyRunningVo;
import com.xzzn.ems.mapper.EmsAmmeterDataMapper; import com.xzzn.ems.mapper.EmsAmmeterDataMapper;
import com.xzzn.ems.mapper.EmsBatteryStackMapper; import com.xzzn.ems.mapper.EmsBatteryStackMapper;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsPcsSettingMapper; import com.xzzn.ems.mapper.EmsPcsSettingMapper;
import com.xzzn.ems.mapper.EmsStrategyLogMapper; import com.xzzn.ems.mapper.EmsStrategyLogMapper;
import com.xzzn.ems.mapper.EmsStrategyRuntimeConfigMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper; import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper; import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.mapper.EmsStrategyTimeConfigMapper; 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.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
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.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import javax.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -55,16 +69,34 @@ public class StrategyPoller {
private static final ConcurrentHashMap<Long, Boolean> strategyLocks = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<Long, Boolean> strategyLocks = new ConcurrentHashMap<>();
// SOC 上下限值默认为0%-100% // SOC 上下限值默认为0%-100%
private static final BigDecimal SOC_DOWN = new BigDecimal(0); private static final BigDecimal DEFAULT_SOC_DOWN = BigDecimal.ZERO;
private static final BigDecimal SOC_UP = new BigDecimal(100); private static final BigDecimal DEFAULT_SOC_UP = new BigDecimal(100);
// 逆变器功率下限值默认为30kW // 逆变器功率下限值默认为30kW
private static final BigDecimal ANTI_REVERSE_THRESHOLD = new BigDecimal(30); private static final BigDecimal DEFAULT_ANTI_REVERSE_THRESHOLD = new BigDecimal(30);
// 逆变器下限值范围默认为20% // 逆变器下限值范围默认为20%
private static final BigDecimal ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20); private static final BigDecimal DEFAULT_ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20);
// 逆变器功率上限值,默认为50kW // 逆变器功率上限值,默认为100kW
private static final BigDecimal ANTI_REVERSE_UP = new BigDecimal(50); private static final BigDecimal DEFAULT_ANTI_REVERSE_UP = new BigDecimal(100);
// PCS功率降幅默认为10% // PCS功率降幅默认为10%
private static final BigDecimal ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(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 @Autowired
private EmsStrategyRunningMapper emsStrategyRunningMapper; private EmsStrategyRunningMapper emsStrategyRunningMapper;
@ -83,7 +115,16 @@ public class StrategyPoller {
@Autowired @Autowired
private EmsStrategyLogMapper emsStrategyLogMapper; private EmsStrategyLogMapper emsStrategyLogMapper;
@Autowired @Autowired
private EmsStrategyRuntimeConfigMapper runtimeConfigMapper;
@Autowired
private RedisCache redisCache;
@Autowired
private ModbusProcessor modbusProcessor; private ModbusProcessor modbusProcessor;
@Autowired
private ISysOperLogService operLogService;
@Resource(name = "modbusExecutor")
private ExecutorService modbusExecutor;
public void pollAllDevices() { public void pollAllDevices() {
logger.info("开始执行运行策略数据轮询..."); logger.info("开始执行运行策略数据轮询...");
@ -91,22 +132,17 @@ public class StrategyPoller {
strategyRunningVoList.forEach(strategyVo -> { strategyRunningVoList.forEach(strategyVo -> {
Long strategyId = strategyVo.getId(); Long strategyId = strategyVo.getId();
if (strategyLocks.putIfAbsent(strategyId, true) == null) { if (strategyLocks.putIfAbsent(strategyId, true) == null) {
try { // 使用共享的modbusExecutor串行执行避免与ModbusPoller并发访问导致通讯故障
CompletableFuture.runAsync(() -> { modbusExecutor.submit(() -> {
processData(strategyVo); try {
}) processData(strategyVo);
.exceptionally(e -> { } catch (Exception e) {
logger.error("运行策略{}轮询异常", strategyVo.getId(), e); logger.error("运行策略{}轮询异常", strategyVo.getId(), e);
return null; } finally {
}) logger.info("运行策略{}轮询任务执行完成,释放锁", strategyVo.getId());
.thenRun(() -> { strategyLocks.remove(strategyId);
logger.info("运行策略{}轮询任务执行完成,释放锁", strategyVo.getId()); }
strategyLocks.remove(strategyId); });
});
} catch (Exception e) {
logger.error("运行策略{}任务失败", strategyVo.getId(), e);
strategyLocks.remove(strategyId);
}
} else { } else {
logger.info("策略{}已在处理中,跳过重复执行", strategyId); logger.info("策略{}已在处理中,跳过重复执行", strategyId);
} }
@ -132,6 +168,7 @@ public class StrategyPoller {
} }
private void dealStrategyCurveData(Long strategyId, String siteId) { private void dealStrategyCurveData(Long strategyId, String siteId) {
EmsStrategyRuntimeConfig runtimeConfig = getRuntimeConfig(siteId);
// 1.获取当前策略的所有模板 // 1.获取当前策略的所有模板
List<Map<String, String>> temps = emsStrategyTempMapper.getTempNameList(strategyId, siteId); List<Map<String, String>> temps = emsStrategyTempMapper.getTempNameList(strategyId, siteId);
if (CollectionUtils.isEmpty(temps)) { if (CollectionUtils.isEmpty(temps)) {
@ -161,6 +198,7 @@ public class StrategyPoller {
} }
// 判断当前时间是否在时间段内 // 判断当前时间是否在时间段内
if (!isTimeInRange(LocalTime.now(), emsStrategyTemp.getStartTime(), emsStrategyTemp.getEndTime())) { if (!isTimeInRange(LocalTime.now(), emsStrategyTemp.getStartTime(), emsStrategyTemp.getEndTime())) {
logger.info("当前站点: {}, 策略: {}, 时间段:{} 不在时间段内", siteId, strategyId, emsStrategyTemp.getStartTime() + "-" + emsStrategyTemp.getEndTime());
continue; continue;
} }
// 查询PCS设备信息 // 查询PCS设备信息
@ -173,24 +211,53 @@ public class StrategyPoller {
continue; continue;
} }
// 判断SOC上下限 // 判断SOC上下限
if (isSocInRange(emsStrategyTemp)) { if (isSocInRange(emsStrategyTemp, runtimeConfig)) {
BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower().divide(new BigDecimal(pcsDeviceList.size())); 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();
}
}
for (EmsDevicesSetting pcsDevice : pcsDeviceList) { for (EmsDevicesSetting pcsDevice : pcsDeviceList) {
EmsPcsSetting pcsSetting = emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(pcsDevice.getId()); EmsPcsSetting pcsSetting = pcsSettingCache.computeIfAbsent(
pcsDevice.getId(),
id -> emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(id)
);
if (pcsSetting == null || pcsSetting.getClusterNum() < 1) { if (pcsSetting == null || pcsSetting.getClusterNum() < 1) {
logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId()); logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId());
continue; continue;
} }
// 功率默认放大10倍平均功率值根据电池簇数量进行平均分配 // 平均功率值根据倍率放大后,再按电池簇数量平均分配
avgChargeDischargePower = avgChargeDischargePower.multiply(new BigDecimal(10)).divide(new BigDecimal(pcsSetting.getClusterNum())); BigDecimal strategyPower = avgChargeDischargePower.multiply(runtimeConfig.getPowerSetMultiplier())
.divide(new BigDecimal(pcsSetting.getClusterNum()), POWER_SCALE, RoundingMode.HALF_UP);
// 根据充电状态,处理数据 // 根据充电状态,处理数据
if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
StrategyCommandDecision decision = applyProtectionConstraint(
strategyPower,
ChargeStatus.CHARGING,
runtimeConfig,
protectionConstraint
);
// 发送Modbus命令控制设备-充电 // 发送Modbus命令控制设备-充电
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.CHARGING, avgChargeDischargePower, emsStrategyTemp, false, null); sendModbusCommand(
Collections.singletonList(pcsDevice),
pcsSetting,
decision.getChargeStatus(),
decision.getPower(),
emsStrategyTemp,
false,
null
);
} else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { } else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
boolean needAntiReverseFlow = false; boolean needAntiReverseFlow = false;
Integer powerDownType = null; Integer powerDownType = null;
BigDecimal chargeDischargePower = avgChargeDischargePower; BigDecimal chargeDischargePower = strategyPower;
// 查询策略运行日志 // 查询策略运行日志
EmsStrategyLog lastStrategyLog = getLastStrategyLog(pcsDevice.getDeviceId(), emsStrategyTemp); EmsStrategyLog lastStrategyLog = getLastStrategyLog(pcsDevice.getDeviceId(), emsStrategyTemp);
if (lastStrategyLog != null) { if (lastStrategyLog != null) {
@ -203,39 +270,69 @@ public class StrategyPoller {
} }
// 查询电网电表的正向有功功率,36kW-50kW范围内稳定运行低于36kW降功率高于50kW增加功率 // 查询电网电表的正向有功功率,36kW-50kW范围内稳定运行低于36kW降功率高于50kW增加功率
EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name()); if (totalActivePower == null) {
if (emsAmmeterData == null || emsAmmeterData.getTotalActivePower() == null) { logger.warn("当前站点: {}, 未获取到最新电表数据,执行保守策略并切换待机", emsStrategyTemp.getSiteId());
logger.info("当前站点: {}, 未获取到最新电表数据", emsStrategyTemp.getSiteId()); sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, true, 0);
continue;
} else { } 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(emsAmmeterData.getTotalActivePower()); needAntiReverseFlow = isNeedAntiReverseFlow(totalActivePower, runtimeConfig);
BigDecimal power = avgChargeDischargePower.multiply(ANTI_REVERSE_POWER_DOWN_PERCENT).divide(new BigDecimal(100)); BigDecimal power = strategyPower.multiply(runtimeConfig.getAntiReversePowerDownPercent())
.divide(new BigDecimal(100), POWER_SCALE, RoundingMode.HALF_UP);
if (needAntiReverseFlow) { if (needAntiReverseFlow) {
// 降功率 // 降功率
chargeDischargePower = chargeDischargePower.subtract(power); chargeDischargePower = chargeDischargePower.subtract(power);
powerDownType = 0; powerDownType = 0;
} else { } else {
// 判断是否需要增加功率, // 判断是否需要增加功率,
if (powerDownType != null && emsAmmeterData.getTotalActivePower().compareTo(ANTI_REVERSE_UP) > 0) { if (powerDownType != null && totalActivePower.compareTo(runtimeConfig.getAntiReverseUp()) > 0) {
if (chargeDischargePower.compareTo(avgChargeDischargePower) == 0) { if (chargeDischargePower.compareTo(targetPower) >= 0) {
// 功率增加到平均值则停止 // 功率增加到限幅值则停止
continue; continue;
} }
// 增加功率 // 增加功率
chargeDischargePower = chargeDischargePower.add(power); chargeDischargePower = chargeDischargePower.add(power);
if (chargeDischargePower.compareTo(targetPower) > 0) {
chargeDischargePower = targetPower;
}
powerDownType = 1; powerDownType = 1;
needAntiReverseFlow = true; needAntiReverseFlow = true;
} }
} }
} }
if (BigDecimal.ZERO.compareTo(chargeDischargePower) == 0) { 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则设备直接待机 // 如果已经降功率到0则设备直接待机
// 发送Modbus命令控制设备-待机 // 发送Modbus命令控制设备-待机
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow, powerDownType); sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow, powerDownType);
} else { } else {
// 发送Modbus命令控制设备-放电 // 发送Modbus命令控制设备-放电
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.DISCHARGING, chargeDischargePower, emsStrategyTemp, needAntiReverseFlow, powerDownType); sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, finalStatus, finalPower, emsStrategyTemp, needAntiReverseFlow, powerDownType);
} }
} else { } else {
// 发送Modbus命令控制设备-待机 // 发送Modbus命令控制设备-待机
@ -296,11 +393,11 @@ public class StrategyPoller {
return emsStrategyLogMapper.getLastStrategyLog(query); return emsStrategyLogMapper.getLastStrategyLog(query);
} }
private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower) { private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower, EmsStrategyRuntimeConfig runtimeConfig) {
// 获取当前设定的防逆流阈值(30kW) // 获取当前设定的防逆流阈值(30kW)
BigDecimal threshold = ANTI_REVERSE_THRESHOLD; BigDecimal threshold = runtimeConfig.getAntiReverseThreshold();
// 计算20%范围的上限(36kW) // 计算20%范围的上限(36kW)
BigDecimal upperLimit = threshold.multiply(ANTI_REVERSE_RANGE_PERCENT).divide(new BigDecimal(100)).add(threshold); BigDecimal upperLimit = threshold.multiply(runtimeConfig.getAntiReverseRangePercent()).divide(new BigDecimal(100)).add(threshold);
// 判断电网电表正向有功功率是否小于36kW(接近30kW的20%范围) // 判断电网电表正向有功功率是否小于36kW(接近30kW的20%范围)
return totalActivePower.compareTo(upperLimit) < 0; return totalActivePower.compareTo(upperLimit) < 0;
@ -408,8 +505,9 @@ public class StrategyPoller {
continue; continue;
} else { } else {
// 充、放电,则先开机设备 // 充、放电,则先开机设备
switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL); if (!switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL)) {
continue; continue;
}
} }
} }
@ -417,13 +515,19 @@ public class StrategyPoller {
if (deviceConfig == null) { if (deviceConfig == null) {
continue; continue;
} }
boolean result = modbusProcessor.writeDataToDevice(deviceConfig); boolean result = modbusProcessor.writeDataToDeviceWithRetry(deviceConfig);
if (!result) { if (!result) {
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, chargeStatus.getInfo()); logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, chargeStatus.getInfo());
recordDeviceOperationLog(siteId, deviceId, "写功率", chargeDischargePower, false, chargeStatus.getInfo() + "功率下发失败");
continue;
} else { } else {
recordDeviceOperationLog(siteId, deviceId, "写功率", chargeDischargePower, true, null);
if (ChargeStatus.STANDBY.equals(chargeStatus)) { if (ChargeStatus.STANDBY.equals(chargeStatus)) {
// 待机,先写功率值,再关机 // 待机,先写功率值,再关机
switchDevice(pcsDevice, pcsSetting, WorkStatus.STOP); if (!switchDevice(pcsDevice, pcsSetting, WorkStatus.STOP)) {
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, WorkStatus.STOP.getInfo());
continue;
}
} }
} }
// 记录策略执行日志 // 记录策略执行日志
@ -432,17 +536,147 @@ public class StrategyPoller {
} }
//设备开关机 //设备开关机
private void switchDevice(EmsDevicesSetting pcsDevice, EmsPcsSetting pcsSetting, WorkStatus workStatus) { private boolean switchDevice(EmsDevicesSetting pcsDevice, EmsPcsSetting pcsSetting, WorkStatus workStatus) {
String siteId = pcsDevice.getSiteId(); String siteId = pcsDevice.getSiteId();
String deviceId = pcsDevice.getDeviceId(); String deviceId = pcsDevice.getDeviceId();
String originalWorkStatus = pcsDevice.getWorkStatus();
pcsDevice.setWorkStatus(workStatus.getCode()); pcsDevice.setWorkStatus(workStatus.getCode());
DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting , null, 1); DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting , null, 1);
if (deviceConfig == null) { if (deviceConfig == null) {
return; pcsDevice.setWorkStatus(originalWorkStatus);
return false;
} }
boolean result = modbusProcessor.writeDataToDevice(deviceConfig); boolean result = modbusProcessor.writeDataToDeviceWithRetry(deviceConfig);
if (!result) { if (!result) {
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceConfig, workStatus.getInfo()); 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;
} }
} }
@ -455,13 +689,17 @@ public class StrategyPoller {
LocalTime endLocalTime = endTime.toInstant() LocalTime endLocalTime = endTime.toInstant()
.atZone(zoneId) .atZone(zoneId)
.toLocalTime(); .toLocalTime();
return now.equals(startLocalTime) || (now.isAfter(startLocalTime) && now.isBefore(endLocalTime)); // 支持跨天时段如23:00-01:00边界采用闭区间
if (!startLocalTime.isAfter(endLocalTime)) {
return !now.isBefore(startLocalTime) && !now.isAfter(endLocalTime);
}
return !now.isBefore(startLocalTime) || !now.isAfter(endLocalTime);
} }
// 判断SOC上限和下限 // 判断SOC上限和下限
private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp) { private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp, EmsStrategyRuntimeConfig runtimeConfig) {
BigDecimal socDown = SOC_DOWN; BigDecimal socDown = runtimeConfig.getSocDown();
BigDecimal socUp = SOC_UP; BigDecimal socUp = runtimeConfig.getSocUp();
if (SocLimit.ON.getCode().equals(emsStrategyTemp.getSdcLimit())) { if (SocLimit.ON.getCode().equals(emsStrategyTemp.getSdcLimit())) {
socDown = emsStrategyTemp.getSdcDown(); socDown = emsStrategyTemp.getSdcDown();
socUp = emsStrategyTemp.getSdcUp(); socUp = emsStrategyTemp.getSdcUp();
@ -485,4 +723,54 @@ public class StrategyPoller {
return true; 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

@ -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,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

@ -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

@ -49,6 +49,10 @@ public class EmsPcsSetting extends BaseEntity
@Excel(name = "关机目标功率") @Excel(name = "关机目标功率")
private BigDecimal stopPower; private BigDecimal stopPower;
/** 目标功率倍率 */
@Excel(name = "目标功率倍率")
private BigDecimal powerMultiplier;
/** 电池簇数 */ /** 电池簇数 */
@Excel(name = "电池簇数") @Excel(name = "电池簇数")
private Integer clusterNum; private Integer clusterNum;
@ -135,6 +139,16 @@ public class EmsPcsSetting extends BaseEntity
return stopPower; return stopPower;
} }
public BigDecimal getPowerMultiplier()
{
return powerMultiplier;
}
public void setPowerMultiplier(BigDecimal powerMultiplier)
{
this.powerMultiplier = powerMultiplier;
}
public void setClusterNum(Integer clusterNum) public void setClusterNum(Integer clusterNum)
{ {
this.clusterNum = clusterNum; this.clusterNum = clusterNum;
@ -165,6 +179,7 @@ public class EmsPcsSetting extends BaseEntity
.append("stopCommand", getStopCommand()) .append("stopCommand", getStopCommand())
.append("startPower", getStartPower()) .append("startPower", getStartPower())
.append("stopPower", getStopPower()) .append("stopPower", getStopPower())
.append("powerMultiplier", getPowerMultiplier())
.append("clusterNum", getClusterNum()) .append("clusterNum", getClusterNum())
.append("clusterPointAddress", getClusterPointAddress()) .append("clusterPointAddress", getClusterPointAddress())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())

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,301 @@
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;
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;
}
@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("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,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

@ -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,9 @@ public class AmmeterRevenueStatisListVo {
/** 组合无功-谷 */ /** 组合无功-谷 */
private BigDecimal reActiveValleyPrice = BigDecimal.ZERO; private BigDecimal reActiveValleyPrice = BigDecimal.ZERO;
/** 实际收益 */
private BigDecimal actualRevenue = BigDecimal.ZERO;
public String getDataTime() { public String getDataTime() {
return dataTime; return dataTime;
} }
@ -49,6 +61,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 +164,12 @@ 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;
}
} }

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

@ -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,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

@ -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,33 @@
package com.xzzn.ems.domain.vo;
import java.util.Date;
public class SiteMonitorDataSaveItemVo {
private String fieldCode;
private String fieldValue;
private Date valueTime;
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,24 @@
package com.xzzn.ems.domain.vo;
import java.util.List;
public class SiteMonitorDataSaveRequest {
private String siteId;
private List<SiteMonitorDataSaveItemVo> items;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public List<SiteMonitorDataSaveItemVo> getItems() {
return items;
}
public void setItems(List<SiteMonitorDataSaveItemVo> items) {
this.items = items;
}
}

View File

@ -20,6 +20,16 @@ public class SiteMonitorHomeVo {
*/ */
private BigDecimal dayDisChargedCap; private BigDecimal dayDisChargedCap;
/**
* 昨日充电量
*/
private BigDecimal yesterdayChargedCap;
/**
* 昨日放电量
*/
private BigDecimal yesterdayDisChargedCap;
/** /**
* 总充电量 * 总充电量
*/ */
@ -75,6 +85,11 @@ public class SiteMonitorHomeVo {
*/ */
private BigDecimal dayRevenue; private BigDecimal dayRevenue;
/**
* 昨日实时收入
*/
private BigDecimal yesterdayRevenue;
public BigDecimal getTotalRevenue() { public BigDecimal getTotalRevenue() {
return totalRevenue; return totalRevenue;
} }
@ -91,6 +106,14 @@ public class SiteMonitorHomeVo {
this.dayRevenue = dayRevenue; this.dayRevenue = dayRevenue;
} }
public BigDecimal getYesterdayRevenue() {
return yesterdayRevenue;
}
public void setYesterdayRevenue(BigDecimal yesterdayRevenue) {
this.yesterdayRevenue = yesterdayRevenue;
}
public BigDecimal getDayChargedCap() { public BigDecimal getDayChargedCap() {
return dayChargedCap; return dayChargedCap;
} }
@ -107,6 +130,22 @@ public class SiteMonitorHomeVo {
this.dayDisChargedCap = dayDisChargedCap; this.dayDisChargedCap = dayDisChargedCap;
} }
public BigDecimal getYesterdayChargedCap() {
return yesterdayChargedCap;
}
public void setYesterdayChargedCap(BigDecimal yesterdayChargedCap) {
this.yesterdayChargedCap = yesterdayChargedCap;
}
public BigDecimal getYesterdayDisChargedCap() {
return yesterdayDisChargedCap;
}
public void setYesterdayDisChargedCap(BigDecimal yesterdayDisChargedCap) {
this.yesterdayDisChargedCap = yesterdayDisChargedCap;
}
public BigDecimal getTotalChargedCap() { public BigDecimal getTotalChargedCap() {
return totalChargedCap; return totalChargedCap;
} }

View File

@ -0,0 +1,27 @@
package com.xzzn.ems.domain.vo;
import java.util.Date;
/**
* 单站监控展示数据(配置项 + 当前值)
*/
public class SiteMonitorProjectDisplayVo extends SiteMonitorProjectPointMappingVo {
private String fieldValue;
private Date valueTime;
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,36 @@
package com.xzzn.ems.domain.vo;
import java.util.List;
public class SiteMonitorProjectPointMappingSaveRequest {
private String siteId;
private List<SiteMonitorProjectPointMappingVo> mappings;
private List<String> deletedFieldCodes;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public List<SiteMonitorProjectPointMappingVo> getMappings() {
return mappings;
}
public void setMappings(List<SiteMonitorProjectPointMappingVo> mappings) {
this.mappings = mappings;
}
public List<String> getDeletedFieldCodes() {
return deletedFieldCodes;
}
public void setDeletedFieldCodes(List<String> deletedFieldCodes) {
this.deletedFieldCodes = deletedFieldCodes;
}
}

View File

@ -0,0 +1,124 @@
package com.xzzn.ems.domain.vo;
public class SiteMonitorProjectPointMappingVo {
private String moduleCode;
private String moduleName;
private String menuCode;
private String menuName;
private String sectionName;
private String fieldCode;
private String fieldName;
private String deviceId;
private String deviceName;
private String dataPoint;
private String fixedDataPoint;
private Integer useFixedDisplay;
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 getFieldCode() {
return fieldCode;
}
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 void setFieldCode(String fieldCode) {
this.fieldCode = fieldCode;
}
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
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 getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
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

@ -6,6 +6,7 @@ import java.math.BigDecimal;
* 单站监控-首页-电池堆点位数据 * 单站监控-首页-电池堆点位数据
*/ */
public class StackPointVo{ public class StackPointVo{
private String statisDate; private String statisDate;
/** SOC */ /** SOC */
private BigDecimal avgSoc; private BigDecimal avgSoc;

View File

@ -0,0 +1,76 @@
package com.xzzn.ems.domain.vo;
public class WeatherSyncResultVo {
private String siteId;
private String startTime;
private String endTime;
private int totalDays;
private int successDays;
private int insertedDays;
private int updatedDays;
private String message;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
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;
}
public int getTotalDays() {
return totalDays;
}
public void setTotalDays(int totalDays) {
this.totalDays = totalDays;
}
public int getSuccessDays() {
return successDays;
}
public void setSuccessDays(int successDays) {
this.successDays = successDays;
}
public int getInsertedDays() {
return insertedDays;
}
public void setInsertedDays(int insertedDays) {
this.insertedDays = insertedDays;
}
public int getUpdatedDays() {
return updatedDays;
}
public void setUpdatedDays(int updatedDays) {
this.updatedDays = updatedDays;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,26 @@
package com.xzzn.ems.domain.vo;
import java.util.List;
public class WorkStatusEnumMappingSaveRequest {
private String siteId;
private List<WorkStatusEnumMappingVo> mappings;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public List<WorkStatusEnumMappingVo> getMappings() {
return mappings;
}
public void setMappings(List<WorkStatusEnumMappingVo> mappings) {
this.mappings = mappings;
}
}

View File

@ -0,0 +1,74 @@
package com.xzzn.ems.domain.vo;
public class WorkStatusEnumMappingVo {
private String deviceCategory;
private String matchField;
private String matchFieldName;
private String enumCode;
private String enumName;
private String enumDesc;
private String dataEnumCode;
public String getEnumCode() {
return enumCode;
}
public void setEnumCode(String enumCode) {
this.enumCode = enumCode;
}
public String getEnumName() {
return enumName;
}
public void setEnumName(String enumName) {
this.enumName = enumName;
}
public String getEnumDesc() {
return enumDesc;
}
public void setEnumDesc(String enumDesc) {
this.enumDesc = enumDesc;
}
public String getDataEnumCode() {
return dataEnumCode;
}
public void setDataEnumCode(String dataEnumCode) {
this.dataEnumCode = dataEnumCode;
}
public String getDeviceCategory() {
return deviceCategory;
}
public void setDeviceCategory(String deviceCategory) {
this.deviceCategory = deviceCategory;
}
public String getMatchField() {
return matchField;
}
public void setMatchField(String matchField) {
this.matchField = matchField;
}
public String getMatchFieldName() {
return matchFieldName;
}
public void setMatchFieldName(String matchFieldName) {
this.matchFieldName = matchFieldName;
}
}

View File

@ -1,9 +1,18 @@
package com.xzzn.ems.mapper; package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.EmsAmmeterData;
import com.xzzn.ems.domain.vo.AmmeterStatisListVo;
import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.domain.vo.MonthlyTimeRange;
import com.xzzn.ems.domain.vo.PowerStatisListVo;
import com.xzzn.ems.domain.vo.SiteMonitorDataVo;
import com.xzzn.ems.domain.vo.StatisAmmeterDateRequest;
import com.xzzn.ems.domain.vo.TimePointQuery;
import com.xzzn.ems.domain.vo.TimePointValue;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import com.xzzn.ems.domain.EmsAmmeterData;
import com.xzzn.ems.domain.vo.*;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
/** /**
@ -99,4 +108,6 @@ public interface EmsAmmeterDataMapper
List<AmmeterStatisListVo> selectDailyAmmeterData(@Param("siteId") String siteId, List<AmmeterStatisListVo> selectDailyAmmeterData(@Param("siteId") String siteId,
@Param("startTime") String startTime, @Param("startTime") String startTime,
@Param("endTime") String endTime); @Param("endTime") String endTime);
List<PowerStatisListVo> getPowerDataByMinute(DateSearchRequest requestVo);
} }

View File

@ -1,12 +1,17 @@
package com.xzzn.ems.mapper; package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.EmsBatteryCluster;
import com.xzzn.ems.domain.vo.BMSBatteryDataList;
import com.xzzn.ems.domain.vo.BatteryAveTempVo;
import com.xzzn.ems.domain.vo.ClusterStatisListVo;
import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.domain.vo.StatisClusterDateRequest;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.xzzn.ems.domain.EmsBatteryCluster;
import com.xzzn.ems.domain.vo.*;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
/** /**
@ -83,4 +88,6 @@ public interface EmsBatteryClusterMapper
public List<Map<String, BigDecimal>> getAvgTempByHour(DateSearchRequest requestVo); public List<Map<String, BigDecimal>> getAvgTempByHour(DateSearchRequest requestVo);
public List<Map<String, BigDecimal>> getAvgTempByDay(DateSearchRequest requestVo); public List<Map<String, BigDecimal>> getAvgTempByDay(DateSearchRequest requestVo);
public List<Map<String, BigDecimal>> getAvgTempByMonth(DateSearchRequest requestVo); public List<Map<String, BigDecimal>> getAvgTempByMonth(DateSearchRequest requestVo);
List<Map<String, BigDecimal>> getAvgTempByMinute(DateSearchRequest requestVo);
} }

View File

@ -1,9 +1,15 @@
package com.xzzn.ems.mapper; package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.EmsBatteryStack;
import com.xzzn.ems.domain.vo.BatteryAveSOCVo;
import com.xzzn.ems.domain.vo.BatteryAveTempVo;
import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.domain.vo.StackPointVo;
import com.xzzn.ems.domain.vo.StackStatisListVo;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import com.xzzn.ems.domain.EmsBatteryStack;
import com.xzzn.ems.domain.vo.*;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
/** /**
@ -85,4 +91,8 @@ public interface EmsBatteryStackMapper
public List<BatteryAveSOCVo> getAveSocList(@Param("siteId") String siteId, @Param("startDate") Date startDate, @Param("endDate") Date endDate); public List<BatteryAveSOCVo> getAveSocList(@Param("siteId") String siteId, @Param("startDate") Date startDate, @Param("endDate") Date endDate);
// 实时运行-DDS平均温度 // 实时运行-DDS平均温度
public List<BatteryAveTempVo> getBatteryAveTempList(@Param("siteId")String siteId, @Param("startDate")Date yesterday, @Param("endDate") Date today); public List<BatteryAveTempVo> getBatteryAveTempList(@Param("siteId")String siteId, @Param("startDate")Date yesterday, @Param("endDate") Date today);
List<StackPointVo> getStackPointByMinute(DateSearchRequest requestVo);
List<StackStatisListVo> getStackDataByMinute(DateSearchRequest requestVo);
} }

View File

@ -69,6 +69,17 @@ public interface EmsDailyChargeDataMapper
// 插入或更新站点每日充放电数据 // 插入或更新站点每日充放电数据
public void insertOrUpdateData(EmsDailyChargeData emsDailyChargeData); public void insertOrUpdateData(EmsDailyChargeData emsDailyChargeData);
// 按站点+日期(天)查询当日已存在记录
public EmsDailyChargeData selectBySiteIdAndDateTime(@Param("siteId") String siteId,
@Param("dateTime") Date dateTime);
// 按站点+日期(天)更新收入字段
public int updateRevenueBySiteAndDate(@Param("siteId") String siteId,
@Param("dateTime") Date dateTime,
@Param("totalRevenue") BigDecimal totalRevenue,
@Param("dayRevenue") BigDecimal dayRevenue,
@Param("updateBy") String updateBy);
// 获取所有站点总充总放 // 获取所有站点总充总放
public Map<String, BigDecimal> getAllSiteChargeData(@Param("nowData")String nowData, @Param("siteId")String siteId); public Map<String, BigDecimal> getAllSiteChargeData(@Param("nowData")String nowData, @Param("siteId")String siteId);

View File

@ -68,6 +68,12 @@ public interface EmsDailyEnergyDataMapper
// 获取站点某日电表数据 // 获取站点某日电表数据
public EmsDailyEnergyData getDataByDate(@Param("siteId")String siteId,@Param("today") String today); public EmsDailyEnergyData getDataByDate(@Param("siteId")String siteId,@Param("today") String today);
// 按站点+日期+小时查询当小时已存在记录
public EmsDailyEnergyData selectBySiteIdAndDateHour(@Param("siteId") String siteId,
@Param("dataDate") java.util.Date dataDate,
@Param("dataHour") Integer dataHour);
// 插入或更新每日尖峰平谷差值 // 插入或更新每日尖峰平谷差值
public void insertOrUpdateData(EmsDailyEnergyData energyData); public void insertOrUpdateData(EmsDailyEnergyData energyData);
// 电表报表 // 电表报表

View File

@ -136,8 +136,12 @@ public interface EmsPcsDataMapper
public List<PcsStatisListVo> getPcsActivePowerByMonth(DateSearchRequest requestVo); public List<PcsStatisListVo> getPcsActivePowerByMonth(DateSearchRequest requestVo);
public List<PcsStatisListVo> getPcsActivePowerByMinutes(DateSearchRequest requestVo);
// 实时运行-fx-pcs最高温度 // 实时运行-fx-pcs最高温度
public List<PcsMaxTempVo> getFXMaxTemp(@Param("siteId")String siteId, @Param("startDate")Date startDate, @Param("endDate")Date endDate); public List<PcsMaxTempVo> getFXMaxTemp(@Param("siteId")String siteId, @Param("startDate")Date startDate, @Param("endDate")Date endDate);
// 实时运行-dds-pcs最高温度 // 实时运行-dds-pcs最高温度
public List<PcsMaxTempVo> getDDSMaxTemp(@Param("siteId")String siteId, @Param("startDate")Date startDate, @Param("endDate")Date endDate); public List<PcsMaxTempVo> getDDSMaxTemp(@Param("siteId")String siteId, @Param("startDate")Date startDate, @Param("endDate")Date endDate);
List<PcsMaxTempVo> getPcsMaxTemp(@Param("siteId") String siteId, @Param("startDate") Date startDate, @Param("endDate") Date endDate);
} }

View File

@ -0,0 +1,24 @@
package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.EmsPointCalcConfig;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface EmsPointCalcConfigMapper {
EmsPointCalcConfig selectEmsPointCalcConfigById(Long id);
List<EmsPointCalcConfig> selectEmsPointCalcConfigList(EmsPointCalcConfig emsPointCalcConfig);
int insertEmsPointCalcConfig(EmsPointCalcConfig emsPointCalcConfig);
int updateEmsPointCalcConfig(EmsPointCalcConfig emsPointCalcConfig);
int deleteEmsPointCalcConfigById(Long id);
int deleteEmsPointCalcConfigByIds(Long[] ids);
int countBySiteId(@Param("siteId") String siteId);
int deleteBySiteId(@Param("siteId") String siteId);
}

View File

@ -0,0 +1,54 @@
package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.EmsPointConfig;
import com.xzzn.ems.domain.vo.GeneralQueryPointOptionVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface EmsPointConfigMapper {
EmsPointConfig selectEmsPointConfigById(Long id);
List<EmsPointConfig> selectEmsPointConfigList(EmsPointConfig emsPointConfig);
int insertEmsPointConfig(EmsPointConfig emsPointConfig);
int insertBatchEmsPointConfig(@Param("list") List<EmsPointConfig> list);
int updateEmsPointConfig(EmsPointConfig emsPointConfig);
int updateEmsPointConfigForImport(EmsPointConfig emsPointConfig);
int deleteEmsPointConfigById(Long id);
int deleteEmsPointConfigByIds(Long[] ids);
int countBySiteId(@Param("siteId") String siteId);
int deleteBySiteId(@Param("siteId") String siteId);
int copyTemplateToSite(@Param("templateSiteId") String templateSiteId,
@Param("targetSiteId") String targetSiteId,
@Param("operName") String operName);
String getRegisterAddress(@Param("siteId") String siteId,
@Param("deviceCategory") String deviceCategory,
@Param("deviceId") String deviceId,
@Param("dataKey") String dataKey);
List<GeneralQueryPointOptionVo> getPointNameList(@Param("siteIds") List<String> siteIds,
@Param("deviceCategory") String deviceCategory,
@Param("deviceId") String deviceId,
@Param("pointName") String pointName);
List<EmsPointConfig> getConfigListForGeneralQuery(@Param("siteIds") List<String> siteIds,
@Param("deviceCategory") String deviceCategory,
@Param("pointIds") List<String> pointIds,
@Param("pointNames") List<String> pointNames,
@Param("deviceIds") List<String> deviceIds);
List<EmsPointConfig> selectBySiteIdAndPointIds(@Param("siteId") String siteId,
@Param("pointIds") List<String> pointIds);
List<EmsPointConfig> selectModbusCollectPointConfigs(@Param("siteId") String siteId);
}

View File

@ -67,4 +67,16 @@ public interface EmsPointEnumMatchMapper
public List<EmsPointEnumMatch> selectList(@Param("siteId") String siteId, public List<EmsPointEnumMatch> selectList(@Param("siteId") String siteId,
@Param("deviceCategory") String deviceCategory, @Param("deviceCategory") String deviceCategory,
@Param("matchField") String matchField); @Param("matchField") String matchField);
int countBySiteId(@Param("siteId") String siteId);
int deleteBySiteId(@Param("siteId") String siteId);
int copyTemplateToSite(@Param("templateSiteId") String templateSiteId,
@Param("targetSiteId") String targetSiteId,
@Param("operName") String operName);
int deleteByScope(@Param("siteId") String siteId,
@Param("deviceCategory") String deviceCategory,
@Param("matchField") String matchField);
} }

View File

@ -75,7 +75,8 @@ public interface EmsPointMatchMapper
// 获取匹配信息 // 获取匹配信息
public List<EmsPointMatch> getMatchInfo(@Param("siteIds") List<String> siteIds, public List<EmsPointMatch> getMatchInfo(@Param("siteIds") List<String> siteIds,
@Param("deviceCategory")String deviceCategory, @Param("deviceId") String deviceId,
@Param("deviceCategory") String deviceCategory,
@Param("pointName") String pointName); @Param("pointName") String pointName);
// 根据条件查询数据-按分钟-单体电池特殊处理 // 根据条件查询数据-按分钟-单体电池特殊处理
public List<GeneralQueryDataVo> getBatteryPointDataByMinutes(@Param("siteIds")List<String> siteIds, public List<GeneralQueryDataVo> getBatteryPointDataByMinutes(@Param("siteIds")List<String> siteIds,
@ -172,4 +173,14 @@ public interface EmsPointMatchMapper
int getDevicePointAlarmNum(@Param("siteId") String siteId, @Param("deviceId") String deviceId, @Param("deviceCategory") String deviceCategory); int getDevicePointAlarmNum(@Param("siteId") String siteId, @Param("deviceId") String deviceId, @Param("deviceCategory") String deviceCategory);
List<EmsPointMatch> selectDeviceStatusPoint(@Param("request") DeviceUpdateRequest request); List<EmsPointMatch> selectDeviceStatusPoint(@Param("request") DeviceUpdateRequest request);
int countBySiteId(@Param("siteId") String siteId);
int deleteBySiteId(@Param("siteId") String siteId);
int copyTemplateToSite(@Param("templateSiteId") String templateSiteId,
@Param("targetSiteId") String targetSiteId,
@Param("operName") String operName);
List<EmsPointMatch> selectBySiteId(@Param("siteId") String siteId);
} }

View File

@ -0,0 +1,23 @@
package com.xzzn.ems.mapper;
import org.apache.ibatis.annotations.Param;
/**
* 单站监控展示数据 Mapper
*/
public interface EmsSiteMonitorDataMapper {
String selectHistoryJsonByMinute(@Param("tableName") String tableName,
@Param("siteId") String siteId,
@Param("statisMinute") java.util.Date statisMinute);
int upsertHistoryJsonByMinute(@Param("tableName") String tableName,
@Param("siteId") String siteId,
@Param("statisMinute") java.util.Date statisMinute,
@Param("dataJson") String dataJson,
@Param("hotSoc") String hotSoc,
@Param("hotTotalActivePower") String hotTotalActivePower,
@Param("hotTotalReactivePower") String hotTotalReactivePower,
@Param("hotDayChargedCap") String hotDayChargedCap,
@Param("hotDayDisChargedCap") String hotDayDisChargedCap,
@Param("operName") String operName);
}

View File

@ -0,0 +1,12 @@
package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.EmsSiteMonitorItem;
import java.util.List;
/**
* 单站监控配置项定义 Mapper
*/
public interface EmsSiteMonitorItemMapper {
List<EmsSiteMonitorItem> selectEnabledList();
}

View File

@ -0,0 +1,21 @@
package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.EmsSiteMonitorPointMatch;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 单站监控字段点位映射 Mapper
*/
public interface EmsSiteMonitorPointMatchMapper {
List<String> selectDistinctSiteIds();
List<EmsSiteMonitorPointMatch> selectBySiteId(@Param("siteId") String siteId);
int deleteBySiteId(@Param("siteId") String siteId);
int deleteBySiteIdAndDeviceId(@Param("siteId") String siteId, @Param("deviceId") String deviceId);
int insertBatch(@Param("list") List<EmsSiteMonitorPointMatch> list);
}

View File

@ -91,5 +91,8 @@ public interface EmsSiteSettingMapper
*/ */
public List<SiteDeviceListVo> getAllSiteDeviceList(@Param("siteId") String siteId, @Param("deviceCategory") String deviceCategory); public List<SiteDeviceListVo> getAllSiteDeviceList(@Param("siteId") String siteId, @Param("deviceCategory") String deviceCategory);
public List<SiteDeviceListVo> getAllSiteDeviceListNoDisp(@Param("siteId") String siteId, @Param("deviceCategory") String deviceCategory);
public List<String> getAllSiteId(); public List<String> getAllSiteId();
} }

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