This commit is contained in:
2026-02-15 16:02:06 +08:00
parent 6253fb6b2d
commit 71d0b0f609
30 changed files with 2844 additions and 799 deletions

View File

@ -15,6 +15,7 @@ import com.xzzn.ems.domain.vo.PointDataRequest;
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.WorkStatusEnumMappingSaveRequest;
import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.IEmsSiteService;
@ -232,6 +233,25 @@ public class EmsSiteConfigController extends BaseController{
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设备开关机
*/

View File

@ -10,10 +10,13 @@ import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.domain.vo.RunningGraphRequest;
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.IEmsStatsReportService;
import com.xzzn.ems.service.ISingleSiteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@ -28,6 +31,8 @@ import java.util.List;
@RestController
@RequestMapping("/ems/siteMonitor")
public class EmsSiteMonitorController extends BaseController{
private static final Logger log = LoggerFactory.getLogger(EmsSiteMonitorController.class);
private static final String RUNNING_GRAPH_CTRL_DEBUG = "RunningGraphCtrlDebug";
@Autowired
private ISingleSiteService iSingleSiteService;
@ -60,27 +65,75 @@ public class EmsSiteMonitorController extends BaseController{
* 单站监控-设备监控-实时运行曲线图数据
*/
@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")
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")
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")
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);
}
/**

View File

@ -128,4 +128,10 @@ public class RedisKeyConstants
/** 单站监控最新数据(按站点+模块) */
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

@ -10,40 +10,14 @@ import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.AlarmLevelStatus;
import com.xzzn.common.enums.AlarmStatus;
import com.xzzn.common.enums.ProtPlanStatus;
import com.xzzn.common.enums.StrategyStatus;
import com.xzzn.common.utils.StringUtils;
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.EmsStrategyRunning;
import com.xzzn.ems.domain.vo.ProtectionPlanVo;
import com.xzzn.ems.domain.vo.ProtectionConstraintVo;
import com.xzzn.ems.domain.vo.ProtectionSettingVo;
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.EmsStrategyRunningMapper;
import com.xzzn.ems.service.IEmsFaultProtectionPlanService;
import com.xzzn.common.core.modbus.ModbusProcessor;
import com.xzzn.common.core.modbus.domain.DeviceConfig;
import com.xzzn.common.core.modbus.domain.WriteTagConfig;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -51,14 +25,29 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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")
public class ProtectionPlanTask {
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")
private ScheduledExecutorService scheduledExecutorService;
@Autowired
@ -66,18 +55,11 @@ public class ProtectionPlanTask {
@Autowired
private EmsAlarmRecordsMapper emsAlarmRecordsMapper;
@Autowired
private EmsStrategyRunningMapper emsStrategyRunningMapper;
@Autowired
private EmsFaultProtectionPlanMapper emsFaultProtectionPlanMapper;
@Autowired
private RedisCache redisCache;
private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private EmsDevicesSettingMapper emsDevicesSettingMapper;
@Autowired
private ModbusProcessor modbusProcessor;
@Autowired
private EmsFaultIssueLogMapper emsFaultIssueLogMapper;
public ProtectionPlanTask(IEmsFaultProtectionPlanService iEmsFaultProtectionPlanService) {
this.iEmsFaultProtectionPlanService = iEmsFaultProtectionPlanService;
@ -86,258 +68,303 @@ public class ProtectionPlanTask {
public void pollPlanList() {
Long planId = 0L;
try {
// 获取所有方案,轮询
List<EmsFaultProtectionPlan> planList = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(null);
for (EmsFaultProtectionPlan plan : planList) {
planId = plan.getId();
String siteId = plan.getSiteId();
if (StringUtils.isEmpty(siteId)) {
return;
continue;
}
// 保护前提
String protectionSettings = plan.getProtectionSettings();
final List<ProtectionSettingVo> protSettings = objectMapper.readValue(
protectionSettings,
new TypeReference<List<ProtectionSettingVo>>() {}
);
if (protSettings == null) {
return;
}
// 处理告警保护方案
boolean isHighLevel = dealWithProtectionPlan(plan, protSettings);
if (isHighLevel) {
// 触发最高故障等级-结束循环
return;
List<ProtectionSettingVo> protSettings = parseProtectionSettings(plan.getProtectionSettings());
if (CollectionUtils.isEmpty(protSettings)) {
continue;
}
dealWithProtectionPlan(plan, protSettings);
}
refreshProtectionConstraintCache(planList);
} catch (Exception e) {
logger.error("轮询失败,方案id为{}", planId, e);
}
}
// 处理告警保护方案-返回触发下发方案时是否最高等级
// 需要同步云端
@SyncAfterInsert
private boolean dealWithProtectionPlan(EmsFaultProtectionPlan plan, List<ProtectionSettingVo> protSettings) {
private void dealWithProtectionPlan(EmsFaultProtectionPlan plan, List<ProtectionSettingVo> protSettings) {
logger.info("<轮询保护方案> 站点:{}方案ID:{}", plan.getSiteId(), plan.getId());
boolean isHighLevel = false;
String siteId = plan.getSiteId();
final Integer isAlertAlarm = plan.getIsAlert();
final Long status = plan.getStatus();
// 看方案是否启用,走不同判断
if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) {
logger.info("<方案未启用> 站点:{}方案ID:{}", siteId, plan.getId());
// 未启用,获取方案的故障值与最新数据判断是否需要下发方案
if(checkIsNeedIssuedPlan(protSettings, siteId)){
if("3".equals(plan.getFaultLevel())){
isHighLevel = true;//最高故障等级
Integer isAlertAlarm = plan.getIsAlert();
Long status = plan.getStatus();
if (status == null) {
status = ProtPlanStatus.STOP.getCode();
}
// 延时
final int faultDelay = plan.getFaultDelaySeconds().intValue();
ScheduledFuture<?> delayTask = scheduledExecutorService.schedule(() -> {
// 延时后再次确认是否仍满足触发条件(防止期间状态变化)
if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) {
if (checkIsNeedIssuedPlan(protSettings, siteId)) {
// 判断是否需要生成告警
if (isAlertAlarm == 1) {
logger.info("<生成告警> 方案ID:{},站点:{}", plan.getId(), siteId);
EmsAlarmRecords alarmRecords = addAlarmRecord(siteId,plan.getFaultName(),
getAlarmLevel(plan.getFaultLevel()));
int faultDelay = safeDelaySeconds(plan.getFaultDelaySeconds(), 0);
scheduledExecutorService.schedule(() -> {
if (!checkIsNeedIssuedPlan(protSettings, siteId)) {
return;
}
if (Integer.valueOf(1).equals(isAlertAlarm)) {
EmsAlarmRecords alarmRecords = addAlarmRecord(siteId, plan.getFaultName(), getAlarmLevel(plan.getFaultLevel()));
emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords);
}
// 是否有保护方案有则通过modbus连接设备下发方案
String protPlanJson = plan.getProtectionPlan();
if (protPlanJson != null && !protPlanJson.isEmpty() && !"[]".equals(protPlanJson)) {
logger.info("<下发保护方案> 方案内容:{}", protPlanJson);
executeProtectionActions(protPlanJson,siteId,plan.getId(),plan.getFaultLevel()); // 执行Modbus指令
}
// 更新方案状态为“已启用”
logger.info("<方案已启用> 方案ID:{}", plan.getId());
plan.setStatus(ProtPlanStatus.RUNNING.getCode());
plan.setUpdateBy("system");
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
// 更新该站点策略为暂停状态
updateStrategyRunningStatus(siteId, StrategyStatus.SUSPENDED.getCode());
}
refreshSiteProtectionConstraint(siteId);
}, faultDelay, TimeUnit.SECONDS);
}
} else {
logger.info("<方案已启用> 站点:{}方案ID:{}", siteId, plan.getId());
// 已启用,则获取方案的释放值与最新数据判断是否需要取消方案
return;
}
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()));
int releaseDelay = safeDelaySeconds(plan.getReleaseDelaySeconds(), 0);
scheduledExecutorService.schedule(() -> {
if (Integer.valueOf(1).equals(isAlertAlarm)) {
EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord(
siteId,
plan.getFaultName(),
getAlarmLevel(plan.getFaultLevel())
);
if (emsAlarmRecords != null) {
emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode());
emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords);
}
}
// 更新方案状态为“未启用”
logger.info("<方案变更为未启用> 方案ID:{}", plan.getId());
plan.setStatus(ProtPlanStatus.STOP.getCode());
plan.setUpdateBy("system");
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
// 更新该站点策略为启用状态
updateStrategyRunningStatus(siteId, StrategyStatus.RUNNING.getCode());
refreshSiteProtectionConstraint(siteId);
}, releaseDelay, TimeUnit.SECONDS);
}
}
return isHighLevel;
private int safeDelaySeconds(Long delay, int defaultSeconds) {
if (delay == null || delay < 0) {
return defaultSeconds;
}
return delay.intValue();
}
// 下发保护方案
private void executeProtectionActions(String protPlanJson, String siteId, Long planId, Integer faultLevel){
final List<ProtectionPlanVo> protPlanList;
private List<ProtectionSettingVo> parseProtectionSettings(String settingsJson) {
if (StringUtils.isEmpty(settingsJson)) {
return new ArrayList<>();
}
try {
protPlanList = objectMapper.readValue(
protPlanJson,
new TypeReference<List<ProtectionPlanVo>>() {}
);
if (protPlanList == null) {
return;
}
// 遍历保护方案
for (ProtectionPlanVo plan : protPlanList) {
if (StringUtils.isEmpty(plan.getDeviceId()) || StringUtils.isEmpty(plan.getPoint())) {
return;
}
// 给设备发送指令记录日志,并同步云端
EmsFaultIssueLog faultIssueLog = createLogEntity(plan,siteId);
faultIssueLog.setLogLevel(faultLevel);
emsFaultIssueLogMapper.insertEmsFaultIssueLog(faultIssueLog);
// 通过modbus连接设备发送数据
executeSinglePlan(plan,siteId);
}
return objectMapper.readValue(settingsJson, new TypeReference<List<ProtectionSettingVo>>() {});
} catch (Exception e) {
logger.error("下发保护方案失败,方案id为", planId, e);
logger.error("解析保护前提失败json:{}", settingsJson, e);
return new ArrayList<>();
}
}
private EmsFaultIssueLog createLogEntity(ProtectionPlanVo plan,String siteId) {
EmsFaultIssueLog faultIssueLog = new EmsFaultIssueLog();
faultIssueLog.setLogId(UUID.randomUUID().toString());
faultIssueLog.setLogTime(new Date());
faultIssueLog.setSiteId(siteId);
faultIssueLog.setDeviceId(plan.getDeviceId());
faultIssueLog.setPoint(plan.getPoint());
faultIssueLog.setValue(plan.getValue());
faultIssueLog.setCreateBy("sys");
faultIssueLog.setCreateTime(new Date());
return faultIssueLog;
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 executeSinglePlan(ProtectionPlanVo plan, String siteId) throws Exception {
String deviceId = plan.getDeviceId();
// 获取设备地址信息
EmsDevicesSetting device = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, siteId);
if (device == null || StringUtils.isEmpty(device.getIpAddress()) || device.getIpPort()==null) {
logger.warn("设备信息不完整deviceId:{}", deviceId);
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;
}
// 构建设备配置
DeviceConfig config = new DeviceConfig();
config.setHost(device.getIpAddress());
config.setPort(device.getIpPort().intValue());
config.setSlaveId(device.getSlaveId().intValue());
config.setDeviceName(device.getDeviceName());
config.setDeviceNumber(device.getDeviceId());
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);
}
// 构建写入标签配置
WriteTagConfig writeTag = new WriteTagConfig();
writeTag.setAddress(plan.getPoint());
writeTag.setValue(plan.getValue());
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());
List<WriteTagConfig> writeTags = new ArrayList<>();
writeTags.add(writeTag);
config.setWriteTags(writeTags);
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);
}
// 写入数据到设备
boolean success = modbusProcessor.writeDataToDeviceWithRetry(config);
String description = StringUtils.isEmpty(plan.getDescription()) ? "" : plan.getDescription();
BigDecimal ratioByDesc = parseDerateRatio(description);
if (ratioByDesc != null) {
vo.setPowerLimitRatio(minRatio(vo.getPowerLimitRatio(), ratioByDesc));
}
if (!success) {
logger.error("写入失败,设备地址:{}", device.getIpAddress());
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);
}
return vo;
}
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;
}
merged.setLevel(Math.max(nullSafeInt(merged.getLevel()), nullSafeInt(incoming.getLevel())));
merged.setAllowCharge(boolAnd(merged.getAllowCharge(), incoming.getAllowCharge()));
merged.setAllowDischarge(boolAnd(merged.getAllowDischarge(), incoming.getAllowDischarge()));
merged.setForceStandby(boolOr(merged.getForceStandby(), incoming.getForceStandby()));
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());
}
}
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) {
BigDecimal releaseValue = BigDecimal.ZERO;
StringBuilder conditionSb = new StringBuilder();
for (int i = 0; i < protSettings.size(); i++) {
ProtectionSettingVo vo = protSettings.get(i);
String deviceId = vo.getDeviceId();
String point = vo.getPoint();
releaseValue = vo.getFaultValue();
if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || releaseValue == null
BigDecimal releaseValue = vo.getReleaseValue();
if (StringUtils.isEmpty(deviceId)
|| StringUtils.isEmpty(point)
|| releaseValue == null
|| StringUtils.isEmpty(vo.getReleaseOperator())) {
return false;
}
// 获取点位最新值
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
logger.info("checkIsNeedCancelPlan 点位:{},最新值:{},比较方式:{},释放值:{}", point, lastPointValue, vo.getReleaseOperator(), releaseValue);
if (lastPointValue == null) {
return false;
}
// 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系)
conditionSb.append(lastPointValue).append(vo.getReleaseOperator()).append(releaseValue);
if (i < protSettings.size() - 1) {
String relation = vo.getRelationNext();
conditionSb.append(" ").append(relation).append(" ");
conditionSb.append(" ").append(vo.getRelationNext()).append(" ");
}
}
// 执行比较语句
return executeWithParser(conditionSb.toString());
}
// 校验故障值是否需要下发方案
private boolean checkIsNeedIssuedPlan(List<ProtectionSettingVo> protSettings, String siteId) {
BigDecimal faultValue = BigDecimal.ZERO;
StringBuilder conditionSb = new StringBuilder();
for (int i = 0; i < protSettings.size(); i++) {
ProtectionSettingVo vo = protSettings.get(i);
String deviceId = vo.getDeviceId();
String point = vo.getPoint();
faultValue = vo.getFaultValue();
if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || faultValue == null
BigDecimal faultValue = vo.getFaultValue();
if (StringUtils.isEmpty(deviceId)
|| StringUtils.isEmpty(point)
|| faultValue == null
|| StringUtils.isEmpty(vo.getFaultOperator())) {
return false;
}
// 获取点位最新值
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
logger.info("checkIsNeedIssuedPlan 点位:{},最新值:{},比较方式:{},故障值:{}", point, lastPointValue, vo.getFaultOperator(), faultValue);
if (lastPointValue == null) {
return false;
}
// 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系)
conditionSb.append(lastPointValue).append(vo.getFaultOperator()).append(faultValue);
if (i < protSettings.size() - 1) {
String relation = vo.getRelationNext();
conditionSb.append(" ").append(relation).append(" ");
conditionSb.append(" ").append(vo.getRelationNext()).append(" ");
}
}
// 执行比较语句
return executeWithParser(conditionSb.toString());
}
@ -354,34 +381,6 @@ public class ProtectionPlanTask {
return StringUtils.getBigDecimal(obj.get(point));
}
// 更新站点策略为启用
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.setSiteId(siteId);
@ -395,29 +394,31 @@ public class ProtectionPlanTask {
return emsAlarmRecords;
}
// 故障等级-告警等级匹配
private String getAlarmLevel(Integer faultLevel) {
if (ObjectUtils.isEmpty(faultLevel) || faultLevel < 1 || faultLevel > 3) {
logger.warn("非法故障等级:{},默认返回普通告警", faultLevel);
logger.warn("非法故障等级:{},默认返回紧急告警", faultLevel);
return AlarmLevelStatus.EMERGENCY.getCode();
}
switch (faultLevel) {
case 1: return AlarmLevelStatus.GENERAL.getCode();
case 2: return AlarmLevelStatus.SERIOUS.getCode();
case 3: return AlarmLevelStatus.EMERGENCY.getCode();
case 1:
return AlarmLevelStatus.GENERAL.getCode();
case 2:
return AlarmLevelStatus.SERIOUS.getCode();
case 3:
return AlarmLevelStatus.EMERGENCY.getCode();
default:
logger.error("未匹配的故障等级:{}", faultLevel);
return AlarmLevelStatus.EMERGENCY.getCode();
}
}
// 自定义表达式解析器(仅支持简单运算符和逻辑关系)
/**
* 自定义表达式解析器(仅支持简单运算符和逻辑关系)
*/
public boolean executeWithParser(String conditionStr) {
if (conditionStr == null || conditionStr.isEmpty()) {
return false;
}
// 1. 拆分逻辑关系(提取 && 或 ||
List<String> logicRelations = new ArrayList<>();
Pattern logicPattern = Pattern.compile("(&&|\\|\\|)");
Matcher logicMatcher = logicPattern.matcher(conditionStr);
@ -425,10 +426,7 @@ public class ProtectionPlanTask {
logicRelations.add(logicMatcher.group());
}
// 2. 拆分原子条件(如 "3.55>3.52"
String[] atomicConditions = logicPattern.split(conditionStr);
// 3. 解析每个原子条件并计算结果
List<Boolean> atomicResults = new ArrayList<>();
Pattern conditionPattern = Pattern.compile("(\\d+\\.?\\d*)\\s*([><]=?|==)\\s*(\\d+\\.?\\d*)");
for (String atomic : atomicConditions) {
@ -437,11 +435,10 @@ public class ProtectionPlanTask {
logger.error("无效的原子条件:{}", atomic);
return false;
}
double left = Double.parseDouble(matcher.group(1)); // 左值(最新值)
String operator = matcher.group(2); // 运算符
double right = Double.parseDouble(matcher.group(3)); // 右值(故障值)
double left = Double.parseDouble(matcher.group(1));
String operator = matcher.group(2);
double right = Double.parseDouble(matcher.group(3));
// 执行比较
boolean result;
switch (operator) {
case ">":
@ -466,7 +463,6 @@ public class ProtectionPlanTask {
atomicResults.add(result);
}
// 4. 组合原子结果(根据逻辑关系)
boolean finalResult = atomicResults.get(0);
for (int i = 0; i < logicRelations.size(); i++) {
String relation = logicRelations.get(i);

View File

@ -2,9 +2,11 @@ package com.xzzn.quartz.task;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.xzzn.common.constant.RedisKeyConstants;
import com.xzzn.common.core.modbus.ModbusProcessor;
import com.xzzn.common.core.modbus.domain.DeviceConfig;
import com.xzzn.common.core.modbus.domain.WriteTagConfig;
import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.ChargeStatus;
import com.xzzn.common.enums.DeviceCategory;
import com.xzzn.common.enums.SiteDevice;
@ -20,6 +22,7 @@ import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
import com.xzzn.ems.domain.EmsStrategyLog;
import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.EmsStrategyTimeConfig;
import com.xzzn.ems.domain.vo.ProtectionConstraintVo;
import com.xzzn.ems.domain.vo.StrategyRunningVo;
import com.xzzn.ems.mapper.EmsAmmeterDataMapper;
import com.xzzn.ems.mapper.EmsBatteryStackMapper;
@ -73,6 +76,20 @@ public class StrategyPoller {
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;
@ -95,6 +112,8 @@ public class StrategyPoller {
@Autowired
private EmsStrategyRuntimeConfigMapper runtimeConfigMapper;
@Autowired
private RedisCache redisCache;
@Autowired
private ModbusProcessor modbusProcessor;
@Resource(name = "modbusExecutor")
@ -186,6 +205,7 @@ public class StrategyPoller {
}
// 判断SOC上下限
if (isSocInRange(emsStrategyTemp, runtimeConfig)) {
ProtectionConstraintVo protectionConstraint = getProtectionConstraint(siteId);
Map<Long, EmsPcsSetting> pcsSettingCache = new HashMap<>();
BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower()
.divide(new BigDecimal(pcsDeviceList.size()), POWER_SCALE, RoundingMode.HALF_UP);
@ -206,13 +226,27 @@ public class StrategyPoller {
logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId());
continue;
}
// 功率默认放大10倍平均功率值根据电池簇数量进行平均分配
BigDecimal strategyPower = avgChargeDischargePower.multiply(new BigDecimal(10))
// 平均功率值根据倍率放大后,再按电池簇数量平均分配
BigDecimal strategyPower = avgChargeDischargePower.multiply(runtimeConfig.getPowerSetMultiplier())
.divide(new BigDecimal(pcsSetting.getClusterNum()), POWER_SCALE, RoundingMode.HALF_UP);
// 根据充电状态,处理数据
if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
StrategyCommandDecision decision = applyProtectionConstraint(
strategyPower,
ChargeStatus.CHARGING,
runtimeConfig,
protectionConstraint
);
// 发送Modbus命令控制设备-充电
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.CHARGING, strategyPower, emsStrategyTemp, false, null);
sendModbusCommand(
Collections.singletonList(pcsDevice),
pcsSetting,
decision.getChargeStatus(),
decision.getPower(),
emsStrategyTemp,
false,
null
);
} else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
boolean needAntiReverseFlow = false;
Integer powerDownType = null;
@ -277,13 +311,21 @@ public class StrategyPoller {
if (chargeDischargePower.compareTo(BigDecimal.ZERO) < 0) {
chargeDischargePower = BigDecimal.ZERO;
}
if (BigDecimal.ZERO.compareTo(chargeDischargePower) == 0) {
StrategyCommandDecision decision = applyProtectionConstraint(
chargeDischargePower,
ChargeStatus.DISCHARGING,
runtimeConfig,
protectionConstraint
);
ChargeStatus finalStatus = decision.getChargeStatus();
BigDecimal finalPower = decision.getPower();
if (ChargeStatus.STANDBY.equals(finalStatus) || BigDecimal.ZERO.compareTo(finalPower) == 0) {
// 如果已经降功率到0则设备直接待机
// 发送Modbus命令控制设备-待机
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow, powerDownType);
} else {
// 发送Modbus命令控制设备-放电
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.DISCHARGING, chargeDischargePower, emsStrategyTemp, needAntiReverseFlow, powerDownType);
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, finalStatus, finalPower, emsStrategyTemp, needAntiReverseFlow, powerDownType);
}
} else {
// 发送Modbus命令控制设备-待机
@ -503,6 +545,103 @@ public class StrategyPoller {
return result;
}
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 (!Integer.valueOf(1).equals(runtimeConfig.getProtectInterveneEnable())) {
return new StrategyCommandDecision(targetStatus, safePower(targetPower));
}
if (constraint == null || nullSafeInt(constraint.getLevel()) <= 0) {
return new StrategyCommandDecision(targetStatus, safePower(targetPower));
}
if (Boolean.TRUE.equals(constraint.getForceStop()) || Boolean.TRUE.equals(constraint.getForceStandby())) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
if (ChargeStatus.CHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowCharge())) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
if (ChargeStatus.DISCHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowDischarge())) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
BigDecimal ratio = getPowerLimitRatio(constraint, runtimeConfig);
BigDecimal finalPower = safePower(targetPower).multiply(ratio).setScale(POWER_SCALE, RoundingMode.HALF_UP);
if (finalPower.compareTo(BigDecimal.ZERO) <= 0) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
return new StrategyCommandDecision(targetStatus, finalPower);
}
private BigDecimal getPowerLimitRatio(ProtectionConstraintVo constraint, EmsStrategyRuntimeConfig runtimeConfig) {
BigDecimal ratio = constraint.getPowerLimitRatio();
if (ratio == null || ratio.compareTo(BigDecimal.ZERO) < 0 || ratio.compareTo(BigDecimal.ONE) > 0) {
ratio = DEFAULT_PROTECTION_RATIO;
}
if (nullSafeInt(constraint.getLevel()) == 1 && DEFAULT_PROTECTION_RATIO.compareTo(ratio) == 0) {
BigDecimal deratePercent = runtimeConfig.getProtectL1DeratePercent();
if (deratePercent == null || deratePercent.compareTo(BigDecimal.ZERO) < 0 || deratePercent.compareTo(new BigDecimal("100")) > 0) {
deratePercent = DEFAULT_PROTECT_L1_DERATE_PERCENT;
}
ratio = deratePercent.divide(new BigDecimal("100"), POWER_SCALE, RoundingMode.HALF_UP);
}
return ratio;
}
private BigDecimal safePower(BigDecimal power) {
if (power == null || power.compareTo(BigDecimal.ZERO) < 0) {
return BigDecimal.ZERO;
}
return power;
}
private int nullSafeInt(Integer value) {
return value == null ? 0 : value;
}
private static class StrategyCommandDecision {
private final ChargeStatus chargeStatus;
private final BigDecimal power;
private StrategyCommandDecision(ChargeStatus chargeStatus, BigDecimal power) {
this.chargeStatus = chargeStatus;
this.power = power;
}
public ChargeStatus getChargeStatus() {
return chargeStatus;
}
public BigDecimal getPower() {
return power;
}
}
// 判断当前时间是否在时间范围内
private static boolean isTimeInRange(LocalTime now, Date startTime, Date endTime) {
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
@ -573,6 +712,26 @@ public class StrategyPoller {
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

@ -11,6 +11,7 @@ public class EmsSiteMonitorPointMatch extends BaseEntity {
private Long id;
private String siteId;
private String fieldCode;
private String deviceId;
private String dataPoint;
private String fixedDataPoint;
private Integer useFixedDisplay;
@ -47,6 +48,14 @@ public class EmsSiteMonitorPointMatch extends BaseEntity {
this.dataPoint = dataPoint;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getFixedDataPoint() {
return fixedDataPoint;
}

View File

@ -49,6 +49,29 @@ public class EmsStrategyRuntimeConfig extends BaseEntity {
/** 防逆流硬停阈值(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;
@ -122,6 +145,54 @@ public class EmsStrategyRuntimeConfig extends BaseEntity {
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)
@ -134,6 +205,12 @@ public class EmsStrategyRuntimeConfig extends BaseEntity {
.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())

View File

@ -1,10 +1,19 @@
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;
}

View File

@ -15,6 +15,8 @@ public class PointNameRequest {
private String pointName;
private List<String> pointNames;
private String pointId;
private List<String> pointIds;
/** 数据分组 1-分钟 2-小时 3-天 */
private int dataUnit;
@ -59,6 +61,22 @@ public class PointNameRequest {
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() {
return dataUnit;
}

View File

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

View File

@ -16,6 +16,10 @@ public class SiteMonitorProjectPointMappingVo {
private String fieldName;
private String deviceId;
private String deviceName;
private String dataPoint;
private String fixedDataPoint;
@ -86,6 +90,22 @@ public class SiteMonitorProjectPointMappingVo {
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;
}

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

@ -41,6 +41,7 @@ public interface EmsPointConfigMapper {
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);

View File

@ -75,4 +75,8 @@ public interface EmsPointEnumMatchMapper
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

@ -14,11 +14,6 @@ public interface EmsSiteMonitorDataMapper {
@Param("siteId") String siteId,
@Param("statisMinute") java.util.Date statisMinute,
@Param("dataJson") String dataJson,
@Param("operName") String operName);
int updateHistoryHotColumns(@Param("tableName") String tableName,
@Param("siteId") String siteId,
@Param("statisMinute") java.util.Date statisMinute,
@Param("hotSoc") String hotSoc,
@Param("hotTotalActivePower") String hotTotalActivePower,
@Param("hotTotalReactivePower") String hotTotalReactivePower,

View File

@ -10,6 +10,7 @@ import com.xzzn.ems.domain.vo.SiteMonitorDataSaveRequest;
import com.xzzn.ems.domain.vo.SiteMonitorProjectDisplayVo;
import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest;
import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingVo;
import com.xzzn.ems.domain.vo.WorkStatusEnumMappingVo;
import java.util.Date;
import java.util.List;
@ -46,6 +47,10 @@ public interface IEmsDeviceSettingService
public int saveSiteMonitorProjectPointMapping(SiteMonitorProjectPointMappingSaveRequest request, String operName);
public List<WorkStatusEnumMappingVo> getSiteWorkStatusEnumMappings(String siteId);
public int saveSiteWorkStatusEnumMappings(String siteId, List<WorkStatusEnumMappingVo> mappings, String operName);
public List<SiteMonitorProjectDisplayVo> getSiteMonitorProjectDisplay(String siteId);
public int saveSiteMonitorProjectData(SiteMonitorDataSaveRequest request, String operName);

View File

@ -181,6 +181,102 @@ public class InfluxPointDataWriter {
}
}
public List<PointValue> queryCurveDataByPointKey(String siteId, String pointKey, Date startTime, Date endTime) {
if (!enabled) {
return Collections.emptyList();
}
if (isBlank(siteId) || isBlank(pointKey) || startTime == null || endTime == null) {
return Collections.emptyList();
}
String normalizedSiteId = siteId.trim();
String normalizedPointKey = pointKey.trim();
String influxQl = String.format(
"SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" = '%s' " +
"AND time >= %dms AND time <= %dms ORDER BY time ASC",
measurement,
escapeTagValue(normalizedSiteId),
escapeTagValue(normalizedPointKey),
startTime.getTime(),
endTime.getTime()
);
try {
String queryUrl = buildQueryUrl(influxQl);
List<PointValue> values = parseInfluxQlResponse(executeRequestWithResponse(methodOrDefault(readMethod, "GET"), queryUrl));
if (!values.isEmpty()) {
return values;
}
// 兼容 pointId 大小写差异
String regexQuery = String.format(
"SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" =~ /(?i)^%s$/ " +
"AND time >= %dms AND time <= %dms ORDER BY time ASC",
measurement,
escapeTagValue(normalizedSiteId),
escapeRegex(normalizedPointKey),
startTime.getTime(),
endTime.getTime()
);
return parseInfluxQlResponse(
executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(regexQuery))
);
} catch (Exception e) {
log.warn("按 pointKey 查询 InfluxDB 曲线失败: {}", e.getMessage());
return Collections.emptyList();
}
}
public PointValue queryLatestPointValueByPointKey(String siteId, String pointKey, Date startTime, Date endTime) {
if (!enabled) {
return null;
}
if (isBlank(siteId) || isBlank(pointKey) || startTime == null || endTime == null) {
return null;
}
String normalizedSiteId = siteId.trim();
String normalizedPointKey = pointKey.trim();
String influxQl = String.format(
"SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" = '%s' " +
"AND time >= %dms AND time <= %dms ORDER BY time DESC LIMIT 1",
measurement,
escapeTagValue(normalizedSiteId),
escapeTagValue(normalizedPointKey),
startTime.getTime(),
endTime.getTime()
);
try {
List<PointValue> values = parseInfluxQlResponse(
executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(influxQl))
);
if (!values.isEmpty()) {
return values.get(0);
}
String regexQuery = String.format(
"SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" =~ /(?i)^%s$/ " +
"AND time >= %dms AND time <= %dms ORDER BY time DESC LIMIT 1",
measurement,
escapeTagValue(normalizedSiteId),
escapeRegex(normalizedPointKey),
startTime.getTime(),
endTime.getTime()
);
values = parseInfluxQlResponse(
executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(regexQuery))
);
return values.isEmpty() ? null : values.get(0);
} catch (Exception e) {
log.warn("按 pointKey 查询 InfluxDB 最新值失败: {}", e.getMessage());
return null;
}
}
private String buildWriteUrl() {
if (isV2WritePath()) {
return buildV2WriteUrl();
@ -488,6 +584,7 @@ public class InfluxPointDataWriter {
return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'");
}
private String escapeRegex(String value) {
if (value == null) {
return "";
@ -546,6 +643,7 @@ public class InfluxPointDataWriter {
return values;
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}

View File

@ -110,6 +110,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
private static final Pattern DTDC_PATTERN = Pattern.compile("DTDC(\\d+)([A-Za-z]*)");
private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$");
private static final Pattern VARIABLE_SAFE_PATTERN = Pattern.compile("[^A-Za-z0-9_]");
private static final Pattern SIMPLE_CALC_EXPRESSION_PATTERN = Pattern.compile("^[0-9A-Za-z_+\\-*/().\\s]+$");
private static final int POINT_QUEUE_CAPACITY = 100000;
private static final int POINT_FLUSH_BATCH_SIZE = 2000;
private static final int POINT_FLUSH_MAX_DRAIN_PER_RUN = 20000;
@ -176,7 +177,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
@Autowired
private InfluxPointDataWriter influxPointDataWriter;
private final BlockingQueue<PointDataRecord> pointDataQueue = new LinkedBlockingQueue<>(POINT_QUEUE_CAPACITY);
private final Map<String, List<ExpressionToken>> calcExpressionCache = new ConcurrentHashMap<>();
private final Map<String, CompiledExpression> calcExpressionCache = new ConcurrentHashMap<>();
private final ScheduledExecutorService pointDataWriter = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r);
thread.setName("point-data-writer");
@ -287,7 +288,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
if (!uniquePointKeys.add(pointContextKey)) {
continue;
}
if (isCalcPoint(pointConfig)) {
if (isComputedPoint(pointConfig)) {
continue;
}
dataPointConfigs.add(pointConfig);
@ -383,7 +384,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
List<EmsPointConfig> pointConfigs = getPointConfigsWithCache(siteId, deviceId, deviceCategory);
if (!CollectionUtils.isEmpty(pointConfigs)) {
for (EmsPointConfig pointConfig : pointConfigs) {
if (isCalcPoint(pointConfig)) {
if (isComputedPoint(pointConfig)) {
continue;
}
String dataKey = StringUtils.defaultString(pointConfig.getDataKey()).trim().toUpperCase();
@ -438,8 +439,12 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
return StringUtils.isBlank(normalized) ? null : normalized;
}
private boolean isCalcPoint(EmsPointConfig pointConfig) {
return pointConfig != null && "calc".equalsIgnoreCase(pointConfig.getPointType());
private boolean isComputedPoint(EmsPointConfig pointConfig) {
if (pointConfig == null) {
return false;
}
String pointType = StringUtils.defaultString(pointConfig.getPointType()).trim().toLowerCase();
return "calc".equals(pointType);
}
private void enqueuePointData(String siteId, String deviceId, String pointKey, BigDecimal pointValue, Date dataTime) {
@ -521,14 +526,17 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
String calcDataKey = entry.getKey();
EmsPointConfig calcPointConfig = entry.getValue();
try {
BigDecimal calcValue = evaluateCalcExpression(calcPointConfig.getCalcExpression(), contextValues);
contextValues.put(calcDataKey, calcValue);
putPointValueToContext(calcPointConfig, calcValue, contextValues);
ExpressionValue calcValue = evaluateCalcExpression(calcPointConfig.getCalcExpression(), contextValues);
if (calcValue.isNumber()) {
contextValues.put(calcDataKey, calcValue.asNumber());
putPointValueToContext(calcPointConfig, calcValue.asNumber(), contextValues);
}
// 计算点按站点维度统一落库,不再按配置中的 device_id 分流
String pointId = resolveInfluxPointKey(calcPointConfig);
if (StringUtils.isNotBlank(pointId)) {
enqueuePointData(siteId, deviceId, pointId, calcValue, dataUpdateTime);
calcPointIdValueMap.put(pointId, calcValue);
BigDecimal storedValue = calcValue.asNumber();
enqueuePointData(siteId, deviceId, pointId, storedValue, dataUpdateTime);
calcPointIdValueMap.put(pointId, storedValue);
}
finishedKeys.add(calcDataKey);
progressed = true;
@ -584,213 +592,19 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
return StringUtils.isNotBlank(pointId) ? pointId : null;
}
private BigDecimal evaluateCalcExpression(String expression, Map<String, BigDecimal> contextValues) {
private ExpressionValue evaluateCalcExpression(String expression, Map<String, BigDecimal> contextValues) {
if (StringUtils.isBlank(expression)) {
throw new IllegalArgumentException("计算表达式为空");
}
List<ExpressionToken> rpnTokens = calcExpressionCache.computeIfAbsent(expression, this::compileExpressionToRpn);
return evaluateRpnTokens(rpnTokens, contextValues == null ? Collections.emptyMap() : contextValues);
if (!SIMPLE_CALC_EXPRESSION_PATTERN.matcher(expression).matches()) {
throw new IllegalArgumentException("计算表达式仅支持四则运算");
}
CompiledExpression compiledExpression = calcExpressionCache.computeIfAbsent(expression, this::compileExpression);
return compiledExpression.evaluate(contextValues == null ? Collections.emptyMap() : contextValues);
}
private List<ExpressionToken> compileExpressionToRpn(String expression) {
if (StringUtils.isBlank(expression)) {
throw new IllegalArgumentException("计算表达式为空");
}
List<ExpressionToken> output = new ArrayList<>();
Deque<ExpressionToken> operators = new ArrayDeque<>();
int index = 0;
ExpressionToken previous = null;
while (index < expression.length()) {
char ch = expression.charAt(index);
if (Character.isWhitespace(ch)) {
index++;
continue;
}
if (Character.isDigit(ch) || ch == '.') {
int start = index;
boolean hasDot = ch == '.';
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isDigit(next)) {
index++;
continue;
}
if (next == '.' && !hasDot) {
hasDot = true;
index++;
continue;
}
break;
}
String numberText = expression.substring(start, index);
try {
output.add(ExpressionToken.number(new BigDecimal(numberText)));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("数值格式错误: " + numberText);
}
previous = output.get(output.size() - 1);
continue;
}
if (Character.isLetter(ch) || ch == '_') {
int start = index;
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isLetterOrDigit(next) || next == '_') {
index++;
continue;
}
break;
}
String variable = expression.substring(start, index).trim().toUpperCase();
output.add(ExpressionToken.variable(variable));
previous = output.get(output.size() - 1);
continue;
}
if (ch == '(') {
operators.push(ExpressionToken.leftParen());
previous = ExpressionToken.leftParen();
index++;
continue;
}
if (ch == ')') {
boolean matched = false;
while (!operators.isEmpty()) {
ExpressionToken token = operators.pop();
if (token.getType() == ExpressionTokenType.LEFT_PAREN) {
matched = true;
break;
}
output.add(token);
}
if (!matched) {
throw new IllegalArgumentException("括号不匹配");
}
previous = ExpressionToken.rightParen();
index++;
continue;
}
if (isOperator(ch)) {
boolean unaryMinus = ch == '-' && (previous == null
|| previous.getType() == ExpressionTokenType.OPERATOR
|| previous.getType() == ExpressionTokenType.LEFT_PAREN);
String operatorText = unaryMinus ? "~" : String.valueOf(ch);
ExpressionToken currentOperator = ExpressionToken.operator(operatorText);
while (!operators.isEmpty() && operators.peek().getType() == ExpressionTokenType.OPERATOR) {
ExpressionToken top = operators.peek();
if (shouldPopOperator(currentOperator, top)) {
output.add(operators.pop());
} else {
break;
}
}
operators.push(currentOperator);
previous = currentOperator;
index++;
continue;
}
throw new IllegalArgumentException("表达式包含非法字符: " + ch);
}
while (!operators.isEmpty()) {
ExpressionToken token = operators.pop();
if (token.getType() == ExpressionTokenType.LEFT_PAREN) {
throw new IllegalArgumentException("括号不匹配");
}
output.add(token);
}
return output;
}
private BigDecimal evaluateRpnTokens(List<ExpressionToken> rpnTokens, Map<String, BigDecimal> contextValues) {
Deque<BigDecimal> values = new ArrayDeque<>();
for (ExpressionToken token : rpnTokens) {
if (token.getType() == ExpressionTokenType.NUMBER) {
values.push(token.getNumber());
continue;
}
if (token.getType() == ExpressionTokenType.VARIABLE) {
BigDecimal variableValue = contextValues.get(token.getText());
if (variableValue == null) {
throw new MissingVariableException(token.getText());
}
values.push(variableValue);
continue;
}
if (token.getType() != ExpressionTokenType.OPERATOR) {
throw new IllegalArgumentException("表达式令牌类型不合法: " + token.getType());
}
if ("~".equals(token.getText())) {
if (values.isEmpty()) {
throw new IllegalArgumentException("表达式不合法,缺少操作数");
}
values.push(values.pop().negate());
continue;
}
if (values.size() < 2) {
throw new IllegalArgumentException("表达式不合法,缺少操作数");
}
BigDecimal right = values.pop();
BigDecimal left = values.pop();
values.push(applyOperator(left, right, token.getText()));
}
if (values.size() != 1) {
throw new IllegalArgumentException("表达式不合法,无法归约到单值");
}
return values.pop();
}
private BigDecimal applyOperator(BigDecimal left, BigDecimal right, String operator) {
switch (operator) {
case "+":
return left.add(right);
case "-":
return left.subtract(right);
case "*":
return left.multiply(right);
case "/":
if (BigDecimal.ZERO.compareTo(right) == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return left.divide(right, 10, RoundingMode.HALF_UP);
default:
throw new IllegalArgumentException("不支持的操作符: " + operator);
}
}
private boolean shouldPopOperator(ExpressionToken currentOperator, ExpressionToken stackOperator) {
if (currentOperator == null || stackOperator == null) {
return false;
}
int currentPriority = getOperatorPriority(currentOperator.getText());
int stackPriority = getOperatorPriority(stackOperator.getText());
if (isRightAssociative(currentOperator.getText())) {
return stackPriority > currentPriority;
}
return stackPriority >= currentPriority;
}
private int getOperatorPriority(String operator) {
if ("~".equals(operator)) {
return 3;
}
if ("*".equals(operator) || "/".equals(operator)) {
return 2;
}
if ("+".equals(operator) || "-".equals(operator)) {
return 1;
}
return 0;
}
private boolean isRightAssociative(String operator) {
return "~".equals(operator);
}
private boolean isOperator(char ch) {
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
private CompiledExpression compileExpression(String expression) {
return new CompiledExpression(expression);
}
private void flushPointDataSafely() {
@ -864,7 +678,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
}
Map<String, EmsPointConfig> uniqueByPointId = new LinkedHashMap<>();
for (EmsPointConfig calcPoint : calcPoints) {
if (!isCalcPoint(calcPoint)) {
if (!isComputedPoint(calcPoint)) {
continue;
}
String calcKey = resolvePointContextKey(calcPoint);
@ -925,56 +739,622 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
}
}
private enum ExpressionTokenType {
private enum ExprTokenType {
NUMBER,
VARIABLE,
STRING,
IDENTIFIER,
OPERATOR,
LEFT_PAREN,
RIGHT_PAREN
RIGHT_PAREN,
COMMA,
QUESTION,
COLON,
EOF
}
private static class ExpressionToken {
private final ExpressionTokenType type;
private static class ExprToken {
private final ExprTokenType type;
private final String text;
private final BigDecimal number;
private final String stringValue;
private ExpressionToken(ExpressionTokenType type, String text, BigDecimal number) {
private ExprToken(ExprTokenType type, String text, BigDecimal number, String stringValue) {
this.type = type;
this.text = text;
this.number = number;
this.stringValue = stringValue;
}
private static ExpressionToken number(BigDecimal value) {
return new ExpressionToken(ExpressionTokenType.NUMBER, null, value);
private static ExprToken number(BigDecimal value) {
return new ExprToken(ExprTokenType.NUMBER, null, value, null);
}
private static ExpressionToken variable(String variable) {
return new ExpressionToken(ExpressionTokenType.VARIABLE, variable, null);
private static ExprToken string(String value) {
return new ExprToken(ExprTokenType.STRING, null, null, value);
}
private static ExpressionToken operator(String operator) {
return new ExpressionToken(ExpressionTokenType.OPERATOR, operator, null);
private static ExprToken identifier(String value) {
return new ExprToken(ExprTokenType.IDENTIFIER, value, null, null);
}
private static ExpressionToken leftParen() {
return new ExpressionToken(ExpressionTokenType.LEFT_PAREN, "(", null);
private static ExprToken operator(String value) {
return new ExprToken(ExprTokenType.OPERATOR, value, null, null);
}
private static ExpressionToken rightParen() {
return new ExpressionToken(ExpressionTokenType.RIGHT_PAREN, ")", null);
private static ExprToken symbol(ExprTokenType type, String text) {
return new ExprToken(type, text, null, null);
}
}
private ExpressionTokenType getType() {
return type;
private static class ExpressionValue {
private final BigDecimal numberValue;
private final String textValue;
private ExpressionValue(BigDecimal numberValue, String textValue) {
this.numberValue = numberValue;
this.textValue = textValue;
}
private String getText() {
return text;
private static ExpressionValue ofNumber(BigDecimal numberValue) {
return new ExpressionValue(numberValue, null);
}
private BigDecimal getNumber() {
return number;
private static ExpressionValue ofText(String textValue) {
return new ExpressionValue(null, textValue == null ? "" : textValue);
}
private boolean isNumber() {
return numberValue != null;
}
private BigDecimal asNumber() {
if (numberValue == null) {
throw new IllegalArgumentException("表达式值不是数值类型: " + textValue);
}
return numberValue;
}
private String asText() {
return numberValue != null ? numberValue.stripTrailingZeros().toPlainString() : textValue;
}
private boolean asBoolean() {
return isNumber() ? BigDecimal.ZERO.compareTo(numberValue) != 0 : StringUtils.isNotBlank(textValue);
}
}
private interface ExpressionNode {
ExpressionValue evaluate(Map<String, BigDecimal> contextValues);
}
private static class NumberNode implements ExpressionNode {
private final BigDecimal value;
private NumberNode(BigDecimal value) {
this.value = value;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
return ExpressionValue.ofNumber(value);
}
}
private static class StringNode implements ExpressionNode {
private final String value;
private StringNode(String value) {
this.value = value;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
return ExpressionValue.ofText(value);
}
}
private static class VariableNode implements ExpressionNode {
private final String name;
private VariableNode(String name) {
this.name = name;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
BigDecimal value = contextValues.get(name);
if (value == null) {
throw new MissingVariableException(name);
}
return ExpressionValue.ofNumber(value);
}
}
private static class UnaryNode implements ExpressionNode {
private final String operator;
private final ExpressionNode node;
private UnaryNode(String operator, ExpressionNode node) {
this.operator = operator;
this.node = node;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
ExpressionValue value = node.evaluate(contextValues);
switch (operator) {
case "+":
return value;
case "-":
return ExpressionValue.ofNumber(value.asNumber().negate());
case "!":
return value.asBoolean() ? ExpressionValue.ofNumber(BigDecimal.ZERO) : ExpressionValue.ofNumber(BigDecimal.ONE);
default:
throw new IllegalArgumentException("不支持的一元操作符: " + operator);
}
}
}
private static class BinaryNode implements ExpressionNode {
private final String operator;
private final ExpressionNode left;
private final ExpressionNode right;
private BinaryNode(String operator, ExpressionNode left, ExpressionNode right) {
this.operator = operator;
this.left = left;
this.right = right;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
if ("&&".equals(operator)) {
ExpressionValue leftValue = left.evaluate(contextValues);
if (!leftValue.asBoolean()) {
return ExpressionValue.ofNumber(BigDecimal.ZERO);
}
return right.evaluate(contextValues).asBoolean()
? ExpressionValue.ofNumber(BigDecimal.ONE)
: ExpressionValue.ofNumber(BigDecimal.ZERO);
}
if ("||".equals(operator)) {
ExpressionValue leftValue = left.evaluate(contextValues);
if (leftValue.asBoolean()) {
return ExpressionValue.ofNumber(BigDecimal.ONE);
}
return right.evaluate(contextValues).asBoolean()
? ExpressionValue.ofNumber(BigDecimal.ONE)
: ExpressionValue.ofNumber(BigDecimal.ZERO);
}
ExpressionValue leftValue = left.evaluate(contextValues);
ExpressionValue rightValue = right.evaluate(contextValues);
switch (operator) {
case "+":
if (!leftValue.isNumber() || !rightValue.isNumber()) {
return ExpressionValue.ofText(leftValue.asText() + rightValue.asText());
}
return ExpressionValue.ofNumber(leftValue.asNumber().add(rightValue.asNumber()));
case "-":
return ExpressionValue.ofNumber(leftValue.asNumber().subtract(rightValue.asNumber()));
case "*":
return ExpressionValue.ofNumber(leftValue.asNumber().multiply(rightValue.asNumber()));
case "/":
if (BigDecimal.ZERO.compareTo(rightValue.asNumber()) == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return ExpressionValue.ofNumber(leftValue.asNumber().divide(rightValue.asNumber(), 10, RoundingMode.HALF_UP));
case ">":
return leftValue.asNumber().compareTo(rightValue.asNumber()) > 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case ">=":
return leftValue.asNumber().compareTo(rightValue.asNumber()) >= 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case "<":
return leftValue.asNumber().compareTo(rightValue.asNumber()) < 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case "<=":
return leftValue.asNumber().compareTo(rightValue.asNumber()) <= 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case "==":
if (leftValue.isNumber() && rightValue.isNumber()) {
return leftValue.asNumber().compareTo(rightValue.asNumber()) == 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
}
return Objects.equals(leftValue.asText(), rightValue.asText())
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case "!=":
if (leftValue.isNumber() && rightValue.isNumber()) {
return leftValue.asNumber().compareTo(rightValue.asNumber()) != 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
}
return !Objects.equals(leftValue.asText(), rightValue.asText())
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
default:
throw new IllegalArgumentException("不支持的操作符: " + operator);
}
}
}
private static class TernaryNode implements ExpressionNode {
private final ExpressionNode condition;
private final ExpressionNode trueNode;
private final ExpressionNode falseNode;
private TernaryNode(ExpressionNode condition, ExpressionNode trueNode, ExpressionNode falseNode) {
this.condition = condition;
this.trueNode = trueNode;
this.falseNode = falseNode;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
return condition.evaluate(contextValues).asBoolean()
? trueNode.evaluate(contextValues)
: falseNode.evaluate(contextValues);
}
}
private static class CompiledExpression {
private final ExpressionNode root;
private CompiledExpression(String expression) {
List<ExprToken> tokens = tokenizeExpression(expression);
ExpressionParser parser = new ExpressionParser(tokens);
this.root = parser.parseExpression();
if (!parser.isEnd()) {
ExprToken token = parser.peek();
throw new IllegalArgumentException("表达式尾部有多余内容: " + token.text);
}
}
private ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
return root.evaluate(contextValues);
}
}
private static class ExpressionParser {
private final List<ExprToken> tokens;
private int index;
private ExpressionParser(List<ExprToken> tokens) {
this.tokens = tokens;
this.index = 0;
}
private ExpressionNode parseExpression() {
return parseTernary();
}
private ExpressionNode parseTernary() {
ExpressionNode condition = parseOr();
if (match(ExprTokenType.QUESTION)) {
ExpressionNode trueNode = parseTernary();
expect(ExprTokenType.COLON, "三元表达式缺少 ':'");
ExpressionNode falseNode = parseTernary();
return new TernaryNode(condition, trueNode, falseNode);
}
return condition;
}
private ExpressionNode parseOr() {
ExpressionNode left = parseAnd();
while (matchOperator("||")) {
left = new BinaryNode("||", left, parseAnd());
}
return left;
}
private ExpressionNode parseAnd() {
ExpressionNode left = parseEquality();
while (matchOperator("&&")) {
left = new BinaryNode("&&", left, parseEquality());
}
return left;
}
private ExpressionNode parseEquality() {
ExpressionNode left = parseComparison();
while (true) {
if (matchOperator("==")) {
left = new BinaryNode("==", left, parseComparison());
continue;
}
if (matchOperator("!=")) {
left = new BinaryNode("!=", left, parseComparison());
continue;
}
break;
}
return left;
}
private ExpressionNode parseComparison() {
ExpressionNode left = parseAddSub();
while (true) {
if (matchOperator(">=")) {
left = new BinaryNode(">=", left, parseAddSub());
continue;
}
if (matchOperator("<=")) {
left = new BinaryNode("<=", left, parseAddSub());
continue;
}
if (matchOperator(">")) {
left = new BinaryNode(">", left, parseAddSub());
continue;
}
if (matchOperator("<")) {
left = new BinaryNode("<", left, parseAddSub());
continue;
}
break;
}
return left;
}
private ExpressionNode parseAddSub() {
ExpressionNode left = parseMulDiv();
while (true) {
if (matchOperator("+")) {
left = new BinaryNode("+", left, parseMulDiv());
continue;
}
if (matchOperator("-")) {
left = new BinaryNode("-", left, parseMulDiv());
continue;
}
break;
}
return left;
}
private ExpressionNode parseMulDiv() {
ExpressionNode left = parseUnary();
while (true) {
if (matchOperator("*")) {
left = new BinaryNode("*", left, parseUnary());
continue;
}
if (matchOperator("/")) {
left = new BinaryNode("/", left, parseUnary());
continue;
}
break;
}
return left;
}
private ExpressionNode parseUnary() {
if (matchOperator("+")) {
return new UnaryNode("+", parseUnary());
}
if (matchOperator("-")) {
return new UnaryNode("-", parseUnary());
}
if (matchOperator("!")) {
return new UnaryNode("!", parseUnary());
}
return parsePrimary();
}
private ExpressionNode parsePrimary() {
ExprToken token = peek();
if (match(ExprTokenType.NUMBER)) {
return new NumberNode(token.number);
}
if (match(ExprTokenType.STRING)) {
return new StringNode(token.stringValue);
}
if (match(ExprTokenType.IDENTIFIER)) {
String identifier = token.text;
if (match(ExprTokenType.LEFT_PAREN)) {
if (!"IF".equalsIgnoreCase(identifier)) {
throw new IllegalArgumentException("不支持的函数: " + identifier);
}
ExpressionNode condition = parseExpression();
expect(ExprTokenType.COMMA, "IF函数参数格式错误缺少第1个逗号");
ExpressionNode trueNode = parseExpression();
expect(ExprTokenType.COMMA, "IF函数参数格式错误缺少第2个逗号");
ExpressionNode falseNode = parseExpression();
expect(ExprTokenType.RIGHT_PAREN, "IF函数缺少右括号");
return new TernaryNode(condition, trueNode, falseNode);
}
return new VariableNode(identifier.toUpperCase());
}
if (match(ExprTokenType.LEFT_PAREN)) {
ExpressionNode nested = parseExpression();
expect(ExprTokenType.RIGHT_PAREN, "括号不匹配,缺少右括号");
return nested;
}
throw new IllegalArgumentException("表达式语法错误,当前位置: " + token.text);
}
private ExprToken peek() {
if (index >= tokens.size()) {
return ExprToken.symbol(ExprTokenType.EOF, "<eof>");
}
return tokens.get(index);
}
private boolean isEnd() {
return peek().type == ExprTokenType.EOF;
}
private boolean match(ExprTokenType type) {
if (peek().type != type) {
return false;
}
index++;
return true;
}
private boolean matchOperator(String operator) {
ExprToken token = peek();
if (token.type != ExprTokenType.OPERATOR || !operator.equals(token.text)) {
return false;
}
index++;
return true;
}
private void expect(ExprTokenType type, String message) {
if (!match(type)) {
throw new IllegalArgumentException(message);
}
}
}
private static List<ExprToken> tokenizeExpression(String expression) {
if (StringUtils.isBlank(expression)) {
throw new IllegalArgumentException("计算表达式为空");
}
List<ExprToken> tokens = new ArrayList<>();
int index = 0;
while (index < expression.length()) {
char ch = expression.charAt(index);
if (Character.isWhitespace(ch)) {
index++;
continue;
}
if (Character.isDigit(ch) || ch == '.') {
int start = index;
boolean hasDot = ch == '.';
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isDigit(next)) {
index++;
continue;
}
if (next == '.' && !hasDot) {
hasDot = true;
index++;
continue;
}
break;
}
String text = expression.substring(start, index);
try {
tokens.add(ExprToken.number(new BigDecimal(text)));
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("数值格式错误: " + text);
}
continue;
}
if (Character.isLetter(ch) || ch == '_') {
int start = index;
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isLetterOrDigit(next) || next == '_') {
index++;
continue;
}
break;
}
String text = expression.substring(start, index).trim().toUpperCase();
tokens.add(ExprToken.identifier(text));
continue;
}
if (ch == '\'' || ch == '"') {
char quote = ch;
index++;
StringBuilder sb = new StringBuilder();
boolean escaped = false;
while (index < expression.length()) {
char current = expression.charAt(index++);
if (escaped) {
switch (current) {
case 'n':
sb.append('\n');
break;
case 't':
sb.append('\t');
break;
case 'r':
sb.append('\r');
break;
case '\\':
sb.append('\\');
break;
case '\'':
sb.append('\'');
break;
case '"':
sb.append('"');
break;
default:
sb.append(current);
break;
}
escaped = false;
continue;
}
if (current == '\\') {
escaped = true;
continue;
}
if (current == quote) {
break;
}
sb.append(current);
}
if (escaped || index > expression.length() || expression.charAt(index - 1) != quote) {
throw new IllegalArgumentException("字符串字面量未闭合");
}
tokens.add(ExprToken.string(sb.toString()));
continue;
}
if (ch == '(') {
tokens.add(ExprToken.symbol(ExprTokenType.LEFT_PAREN, "("));
index++;
continue;
}
if (ch == ')') {
tokens.add(ExprToken.symbol(ExprTokenType.RIGHT_PAREN, ")"));
index++;
continue;
}
if (ch == ',') {
tokens.add(ExprToken.symbol(ExprTokenType.COMMA, ","));
index++;
continue;
}
if (ch == '?') {
tokens.add(ExprToken.symbol(ExprTokenType.QUESTION, "?"));
index++;
continue;
}
if (ch == ':') {
tokens.add(ExprToken.symbol(ExprTokenType.COLON, ":"));
index++;
continue;
}
if (index + 1 < expression.length()) {
String twoChars = expression.substring(index, index + 2);
if ("&&".equals(twoChars)
|| "||".equals(twoChars)
|| ">=".equals(twoChars)
|| "<=".equals(twoChars)
|| "==".equals(twoChars)
|| "!=".equals(twoChars)) {
tokens.add(ExprToken.operator(twoChars));
index += 2;
continue;
}
}
if (ch == '+' || ch == '-' || ch == '*' || ch == '/'
|| ch == '>' || ch == '<' || ch == '!') {
tokens.add(ExprToken.operator(String.valueOf(ch)));
index++;
continue;
}
throw new IllegalArgumentException("表达式包含非法字符: " + ch);
}
tokens.add(ExprToken.symbol(ExprTokenType.EOF, "<eof>"));
return tokens;
}
private JSONArray parseJsonData(String message) {

View File

@ -50,7 +50,6 @@ import java.util.stream.Collectors;
public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
private static final Logger log = LoggerFactory.getLogger(EmsPointConfigServiceImpl.class);
private static final String TEMPLATE_SITE_ID = "DEFAULT";
private static final String SITE_LEVEL_CALC_DEVICE_ID = "SITE_CALC";
private static final int CSV_IMPORT_BATCH_SIZE = 200;
private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$");
private static final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$");
@ -249,19 +248,13 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
return new ArrayList<>();
}
String siteId = StringUtils.trim(request.getSiteId());
String deviceId = StringUtils.trim(request.getDeviceId());
String pointId = StringUtils.trim(request.getPointId());
if (StringUtils.isAnyBlank(siteId, pointId)) {
return new ArrayList<>();
}
EmsPointConfig pointConfig = resolvePointConfigForCurve(siteId, pointId);
String pointType = resolvePointTypeForCurve(request, pointConfig);
String queryDeviceId = resolveCurveDeviceId(pointType, deviceId, pointConfig);
if (StringUtils.isBlank(queryDeviceId)) {
return new ArrayList<>();
}
Date[] range = resolveTimeRange(request);
return queryCurveDataFromInflux(siteId, queryDeviceId, pointId, pointConfig, range[0], range[1]);
return queryCurveDataFromInflux(siteId, pointId, pointConfig, range[0], range[1]);
}
private PointConfigLatestValueVo queryLatestValueFromRedis(PointConfigLatestValueItemVo item,
@ -296,13 +289,13 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
return vo;
}
private List<PointConfigCurveValueVo> queryCurveDataFromInflux(String siteId, String deviceId, String pointId,
private List<PointConfigCurveValueVo> queryCurveDataFromInflux(String siteId, String pointId,
EmsPointConfig pointConfig, Date startTime, Date endTime) {
String influxPointKey = resolveInfluxPointKey(pointConfig, pointId);
if (StringUtils.isBlank(influxPointKey)) {
return new ArrayList<>();
}
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveData(siteId, deviceId, influxPointKey, startTime, endTime);
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveDataByPointKey(siteId, influxPointKey, startTime, endTime);
if (values == null || values.isEmpty()) {
return new ArrayList<>();
}
@ -518,29 +511,6 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
return pointConfigs.get(0);
}
private String resolvePointTypeForCurve(PointConfigCurveRequest request, EmsPointConfig pointConfig) {
if (pointConfig != null && StringUtils.isNotBlank(pointConfig.getPointType())) {
return StringUtils.trim(pointConfig.getPointType()).toLowerCase(Locale.ROOT);
}
if (request == null || StringUtils.isBlank(request.getPointType())) {
return "data";
}
return StringUtils.trim(request.getPointType()).toLowerCase(Locale.ROOT);
}
private String resolveCurveDeviceId(String pointType, String requestDeviceId, EmsPointConfig pointConfig) {
if ("calc".equalsIgnoreCase(StringUtils.defaultString(pointType))) {
return SITE_LEVEL_CALC_DEVICE_ID;
}
if (StringUtils.isNotBlank(requestDeviceId)) {
return StringUtils.trim(requestDeviceId);
}
if (pointConfig != null && StringUtils.isNotBlank(pointConfig.getDeviceId())) {
return StringUtils.trim(pointConfig.getDeviceId());
}
return null;
}
private BigDecimal convertPointValue(BigDecimal sourceValue, EmsPointConfig pointConfig) {
if (sourceValue == null || pointConfig == null) {
return sourceValue;

View File

@ -26,6 +26,12 @@ public class EmsStrategyRuntimeConfigServiceImpl implements IEmsStrategyRuntimeC
private static final BigDecimal DEFAULT_ANTI_REVERSE_UP = new BigDecimal("100");
private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal("10");
private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal("20");
private static final BigDecimal DEFAULT_POWER_SET_MULTIPLIER = new BigDecimal("10");
private static final Integer DEFAULT_PROTECT_INTERVENE_ENABLE = 1;
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";
@Autowired
private EmsStrategyRuntimeConfigMapper runtimeConfigMapper;
@ -83,5 +89,23 @@ public class EmsStrategyRuntimeConfigServiceImpl implements IEmsStrategyRuntimeC
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.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);
}
}
}

View File

@ -49,15 +49,12 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
if (siteIds == null || siteIds.isEmpty()) {
return Collections.emptyList();
}
String deviceCategory = request.getDeviceCategory();
String deviceId = request.getDeviceId();
if ((deviceCategory == null || "".equals(deviceCategory.trim()))
&& (deviceId == null || "".equals(deviceId.trim()))) {
return Collections.emptyList();
}
return emsPointConfigMapper.getPointNameList(siteIds, deviceCategory, deviceId, request.getPointName());
return emsPointConfigMapper.getPointNameList(
siteIds,
request.getDeviceCategory(),
request.getDeviceId(),
request.getPointName()
);
}
@Override
@ -94,15 +91,10 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
}
String deviceCategory = request.getDeviceCategory();
String requestDeviceId = request.getDeviceId();
if ((deviceCategory == null || "".equals(deviceCategory.trim()))
&& (requestDeviceId == null || "".equals(requestDeviceId.trim()))
) {
return Collections.emptyList();
}
List<String> pointIds = resolvePointIds(request);
List<String> pointNames = resolvePointNames(request);
if (pointNames.isEmpty()) {
if (pointIds.isEmpty() && pointNames.isEmpty()) {
return Collections.emptyList();
}
@ -114,17 +106,19 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
endDate = DateUtils.adjustToEndOfDay(request.getEndDate());
}
List<String> selectedDeviceIds = resolveSelectedDeviceIds(request);
List<String> selectedDeviceIds = pointIds.isEmpty() ? resolveSelectedDeviceIds(request) : Collections.emptyList();
List<EmsPointConfig> pointConfigs = emsPointConfigMapper.getConfigListForGeneralQuery(
siteIds, deviceCategory, pointNames, selectedDeviceIds
siteIds, deviceCategory, pointIds, pointNames, selectedDeviceIds
);
if (pointConfigs == null || pointConfigs.isEmpty()) {
return Collections.emptyList();
}
Map<String, String> selectedPointNameById = buildSelectedPointNameById(request);
List<GeneralQueryDataVo> dataVoList = new ArrayList<>();
for (EmsPointConfig pointConfig : pointConfigs) {
dataVoList.addAll(queryPointCurve(pointConfig, request.getDataUnit(), startDate, endDate));
String selectedPointName = selectedPointNameById.get(resolveInfluxPointKey(pointConfig));
dataVoList.addAll(queryPointCurve(pointConfig, request.getDataUnit(), startDate, endDate, selectedPointName));
}
if (dataVoList.isEmpty()) {
@ -154,6 +148,19 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return names.stream().distinct().collect(Collectors.toList());
}
private List<String> resolvePointIds(PointNameRequest request) {
List<String> ids = new ArrayList<>();
if (request.getPointIds() != null && !request.getPointIds().isEmpty()) {
ids.addAll(request.getPointIds());
} else if (request.getPointId() != null && !"".equals(request.getPointId().trim())) {
ids.addAll(Arrays.stream(request.getPointId().split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.toList()));
}
return ids.stream().distinct().collect(Collectors.toList());
}
private List<String> resolveSelectedDeviceIds(PointNameRequest request) {
List<String> selected = new ArrayList<>();
if (request.getDeviceId() != null && !"".equals(request.getDeviceId().trim())) {
@ -175,16 +182,18 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return selected.stream().distinct().collect(Collectors.toList());
}
private List<GeneralQueryDataVo> queryPointCurve(EmsPointConfig config, int dataUnit, Date startDate, Date endDate) {
if (config == null || config.getSiteId() == null || config.getDeviceId() == null) {
private List<GeneralQueryDataVo> queryPointCurve(EmsPointConfig config, int dataUnit, Date startDate, Date endDate,
String selectedPointName) {
if (config == null || config.getSiteId() == null) {
return Collections.emptyList();
}
String influxPointKey = resolveInfluxPointKey(config);
if (influxPointKey == null) {
return Collections.emptyList();
}
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveData(
config.getSiteId(), config.getDeviceId(), influxPointKey, startDate, endDate
// 与点位列表曲线保持一致:按 siteId + pointKey 查询,避免 deviceId 维度导致综合查询漏数
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveDataByPointKey(
config.getSiteId(), influxPointKey, startDate, endDate
);
if (values == null || values.isEmpty()) {
return Collections.emptyList();
@ -197,7 +206,7 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
}
List<GeneralQueryDataVo> result = new ArrayList<>();
String displayDeviceId = buildDisplayDeviceId(config);
String displayDeviceId = buildDisplayDeviceId(config, selectedPointName);
for (Map.Entry<String, Object> entry : latestByBucket.entrySet()) {
GeneralQueryDataVo vo = new GeneralQueryDataVo();
vo.setSiteId(config.getSiteId());
@ -219,10 +228,32 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return null;
}
private String buildDisplayDeviceId(EmsPointConfig config) {
private String buildDisplayDeviceId(EmsPointConfig config, String selectedPointName) {
if (selectedPointName != null && !"".equals(selectedPointName.trim())) {
return selectedPointName.trim();
}
String pointName = config.getPointName() == null || "".equals(config.getPointName().trim())
? config.getDataKey() : config.getPointName().trim();
return config.getDeviceId() + "-" + pointName;
return pointName;
}
private Map<String, String> buildSelectedPointNameById(PointNameRequest request) {
Map<String, String> selectedNameById = new HashMap<>();
if (request == null || request.getPointIds() == null || request.getPointNames() == null) {
return selectedNameById;
}
List<String> pointIds = request.getPointIds();
List<String> pointNames = request.getPointNames();
int size = Math.min(pointIds.size(), pointNames.size());
for (int i = 0; i < size; i++) {
String pointId = pointIds.get(i);
String pointName = pointNames.get(i);
if (pointId == null || "".equals(pointId.trim()) || pointName == null || "".equals(pointName.trim())) {
continue;
}
selectedNameById.put(pointId.trim(), pointName.trim());
}
return selectedNameById;
}
private String formatByDataUnit(Date dataTime, int dataUnit) {

View File

@ -14,7 +14,7 @@ import com.xzzn.ems.domain.EmsCoolingData;
import com.xzzn.ems.domain.EmsDhData;
import com.xzzn.ems.domain.EmsEmsData;
import com.xzzn.ems.domain.EmsPcsBranchData;
import com.xzzn.ems.domain.EmsPcsData;
import com.xzzn.ems.domain.EmsSiteMonitorPointMatch;
import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.EmsXfData;
import com.xzzn.ems.domain.vo.*;
@ -28,12 +28,15 @@ import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsEnergyPriceConfigMapper;
import com.xzzn.ems.mapper.EmsPcsDataMapper;
import com.xzzn.ems.mapper.EmsPointMatchMapper;
import com.xzzn.ems.mapper.EmsSiteMonitorPointMatchMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.service.IEmsEnergyPriceConfigService;
import com.xzzn.ems.service.ISingleSiteService;
import com.xzzn.ems.service.InfluxPointDataWriter;
import com.xzzn.ems.utils.DevicePointMatchDataProcessor;
import java.beans.PropertyDescriptor;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
@ -42,6 +45,7 @@ import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@ -53,7 +57,9 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@ -65,6 +71,25 @@ import org.springframework.util.CollectionUtils;
public class SingleSiteServiceImpl implements ISingleSiteService {
private static final Logger log = LoggerFactory.getLogger(SingleSiteServiceImpl.class);
private static final String RUNNING_GRAPH_DEBUG = "RunningGraphDebug";
private static final String FIELD_CURVE_PCS_ACTIVE_POWER = "SBJK_SSYX__curvePcsActivePower";
private static final String FIELD_CURVE_PCS_REACTIVE_POWER = "SBJK_SSYX__curvePcsReactivePower";
private static final String FIELD_CURVE_PCS_MAX_TEMP = "SBJK_SSYX__curvePcsMaxTemp";
private static final String FIELD_CURVE_BATTERY_AVE_SOC = "SBJK_SSYX__curveBatteryAveSoc";
private static final String FIELD_CURVE_BATTERY_AVE_TEMP = "SBJK_SSYX__curveBatteryAveTemp";
private static final String FIELD_TOTAL_ACTIVE_POWER = "SBJK_SSYX__totalActivePower";
private static final String FIELD_TOTAL_REACTIVE_POWER = "SBJK_SSYX__totalReactivePower";
private static final String FIELD_SOC = "SBJK_SSYX__soc";
private static final String FIELD_HOME_AVG_TEMP = "HOME__avgTemp";
private static final String RUNNING_GRAPH_DEFAULT_DEVICE = "SITE";
private static final int USE_FIXED_DISPLAY_YES = 1;
private static final String DEVICE_INFO_ID = "id";
private static final String DEVICE_INFO_NAME = "deviceName";
private static final String DEVICE_INFO_COMM_STATUS = "communicationStatus";
private static final String DEVICE_INFO_DEVICE_STATUS = "deviceStatus";
private static final Set<String> PCS_DETAIL_META_FIELDS = new HashSet<>(Arrays.asList(
"siteId", "deviceId", "deviceName", "alarmNum", "pcsBranchInfoList", "dataUpdateTime"
));
private static final String CLUSTER_DATA_TEP = "温度";
@ -90,6 +115,10 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
private EmsDevicesSettingMapper emsDevicesSettingMapper;
@Autowired
private EmsPointMatchMapper emsPointMatchMapper;
@Autowired
private EmsSiteMonitorPointMatchMapper emsSiteMonitorPointMatchMapper;
@Autowired
private InfluxPointDataWriter influxPointDataWriter;
@Autowired
private RedisCache redisCache;
@ -217,46 +246,61 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
public SiteMonitorRuningInfoVo getRunningGraphStorage(RunningGraphRequest request) {
SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
log.info("{} storage skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
return siteMonitorRuningInfoVo;
}
// // 时间暂定今日+昨日
// Date today = DateUtils.getNowDate();
// Date yesterday = DateUtils.addDays(today, -1);
Date startDate = request.getStartDate();
Date endDate = request.getEndDate();
String siteId = request.getSiteId();
Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
Date startDate = dateRange[0];
Date endDate = dateRange[1];
List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList);
List<DeviceMeta> pcsDeviceList = getDeviceMetaListByCategory(siteId, DeviceCategory.PCS.getCode());
if (CollectionUtils.isEmpty(pcsDeviceList)) {
pcsDeviceList = Collections.singletonList(new DeviceMeta(RUNNING_GRAPH_DEFAULT_DEVICE, RUNNING_GRAPH_DEFAULT_DEVICE));
}
//pcs有功无功
List<PcsPowerList> pcsPowerList = new ArrayList<>();
List<EnergyStoragePowVo> energyStoragePowList = emsPcsDataMapper.getStoragePowerList(request.getSiteId(), startDate, endDate);
for (DeviceMeta pcsDevice : pcsDeviceList) {
String deviceId = StringUtils.defaultString(pcsDevice.getDeviceId());
String activePointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_ACTIVE_POWER);
String reactivePointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_REACTIVE_POWER);
// List<EnergyStoragePowVo> -> 按pcs的deviceId分组转成List<PcsPowerList>
if (!CollectionUtils.isEmpty(energyStoragePowList)) {
Map<String, List<EnergyStoragePowVo>> dataMap = energyStoragePowList.stream()
.collect(Collectors.groupingBy(
EnergyStoragePowVo::getDeviceId,
Collectors.toList()));
List<InfluxPointDataWriter.PointValue> activeValues = queryInfluxPointValues(siteId, activePointId, startDate, endDate);
List<InfluxPointDataWriter.PointValue> reactiveValues = queryInfluxPointValues(siteId, reactivePointId, startDate, endDate);
Map<Long, BigDecimal> reactiveByTs = reactiveValues.stream()
.filter(v -> v != null && v.getDataTime() != null && v.getPointValue() != null)
.collect(Collectors.toMap(v -> v.getDataTime().getTime(), InfluxPointDataWriter.PointValue::getPointValue, (a, b) -> b));
List<EnergyStoragePowVo> energyStoragePowList = new ArrayList<>();
for (InfluxPointDataWriter.PointValue activeValue : activeValues) {
if (activeValue == null || activeValue.getDataTime() == null || activeValue.getPointValue() == null) {
continue;
}
Date dataTime = activeValue.getDataTime();
EnergyStoragePowVo vo = new EnergyStoragePowVo();
vo.setDeviceId(deviceId);
vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
vo.setGroupTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, dataTime));
vo.setPcsTotalActPower(activeValue.getPointValue());
vo.setPcsTotalReactivePower(reactiveByTs.get(dataTime.getTime()));
energyStoragePowList.add(vo);
}
pcsPowerList = dataMap.entrySet().stream()
.map(entry -> {
PcsPowerList pcdData = new PcsPowerList();
pcdData.setDeviceId(entry.getKey());
pcdData.setEnergyStoragePowList(entry.getValue());
return pcdData;
}).collect(Collectors.toList());
// // 生成时间列表每5分钟一个
// List<LocalDateTime> targetMinutes = new ArrayList<>(12);
// LocalDateTime startLocalDate = DateUtils.toLocalDateTime(startDate).truncatedTo(ChronoUnit.DAYS);
// LocalDateTime endLocalDate = DateUtils.toLocalDateTime(endDate).with(LocalDateTime.now().toLocalTime());
// while (startLocalDate.isBefore(endLocalDate)) {
// targetMinutes.add(startLocalDate);
// startLocalDate = startLocalDate.plusMinutes(5); // 递增5分钟
// }
// // 根据时间列表填充数据
// pcsPowerList = fullFillData(pcsPowerList, targetMinutes);
pcdData.setDeviceId(deviceId);
pcdData.setEnergyStoragePowList(energyStoragePowList);
pcsPowerList.add(pcdData);
}
siteMonitorRuningInfoVo.setPcsPowerList(pcsPowerList);
int pointCount = pcsPowerList.stream()
.filter(item -> item != null && item.getEnergyStoragePowList() != null)
.mapToInt(item -> item.getEnergyStoragePowList().size())
.sum();
log.info("{} storage, siteId={}, startDate={}, endDate={}, deviceCount={}, pointCount={}",
RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pcsPowerList.size(), pointCount);
return siteMonitorRuningInfoVo;
}
@ -316,49 +360,52 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
@Override
public SiteMonitorRuningInfoVo getRunningGraphPcsMaxTemp(RunningGraphRequest request) {
SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
List<PcsMaxTempList> pcsMaxTempList = new ArrayList<>();
// // 时间暂定今日+昨日
// Date today = new Date();
// Date yesterday = DateUtils.addDays(today, -1);
if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
log.info("{} pcsMaxTemp skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
return siteMonitorRuningInfoVo;
}
String siteId = request.getSiteId();
Date startDate = request.getStartDate();
Date endDate = request.getEndDate();
//PCS最高温度list
List<PcsMaxTempVo> pcsMaxTempVos = emsPcsDataMapper.getPcsMaxTemp(siteId, startDate, endDate);
// if (SiteEnum.FX.getCode().equals(siteId)) {
// pcsMaxTempVos = emsPcsDataMapper.getFXMaxTemp(siteId, startDate, endDate);
// } else if (SiteEnum.DDS.getCode().equals(siteId)) {
// pcsMaxTempVos = emsPcsDataMapper.getDDSMaxTemp(siteId, startDate, endDate);
// }
Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
Date startDate = dateRange[0];
Date endDate = dateRange[1];
List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList);
List<DeviceMeta> pcsDeviceList = getDeviceMetaListByCategory(siteId, DeviceCategory.PCS.getCode());
if (CollectionUtils.isEmpty(pcsDeviceList)) {
pcsDeviceList = Collections.singletonList(new DeviceMeta(RUNNING_GRAPH_DEFAULT_DEVICE, RUNNING_GRAPH_DEFAULT_DEVICE));
}
// List<PcsMaxTempVo> -> 按pcs的deviceId分组转成List<PcsMaxTempList>
if (!CollectionUtils.isEmpty(pcsMaxTempVos)) {
Map<String, List<PcsMaxTempVo>> dataMap = pcsMaxTempVos.stream()
.collect(Collectors.groupingBy(
PcsMaxTempVo::getDeviceId,
Collectors.toList()));
pcsMaxTempList = dataMap.entrySet().stream()
.map(entry -> {
List<PcsMaxTempList> pcsMaxTempList = new ArrayList<>();
for (DeviceMeta pcsDevice : pcsDeviceList) {
String deviceId = StringUtils.defaultString(pcsDevice.getDeviceId());
String pointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_MAX_TEMP);
List<InfluxPointDataWriter.PointValue> values = queryInfluxPointValues(siteId, pointId, startDate, endDate);
List<PcsMaxTempVo> pcsMaxTempVos = new ArrayList<>();
for (InfluxPointDataWriter.PointValue value : values) {
if (value == null || value.getDataTime() == null || value.getPointValue() == null) {
continue;
}
Date dataTime = value.getDataTime();
PcsMaxTempVo vo = new PcsMaxTempVo();
vo.setDeviceId(deviceId);
vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
vo.setTemp(value.getPointValue());
pcsMaxTempVos.add(vo);
}
PcsMaxTempList pcdData = new PcsMaxTempList();
pcdData.setDeviceId(entry.getKey());
pcdData.setMaxTempVoList(entry.getValue());
return pcdData;
}).collect(Collectors.toList());
// // 生成时间列表(每小时一个)
// List<LocalDateTime> targetHours = new ArrayList<>(60);
// LocalDateTime startDate = DateUtils.toLocalDateTime(yesterday).truncatedTo(ChronoUnit.DAYS);
// LocalDateTime endDate = DateUtils.toLocalDateTime(today);
// while (startDate.isBefore(endDate)) {
// targetHours.add(startDate);
// startDate = startDate.plusHours(1); // 递增1小时
// }
// // 根据时间列表填充数据
// pcsMaxTempList = fullFillMaxTempData(pcsMaxTempList,targetHours);
pcdData.setDeviceId(deviceId);
pcdData.setMaxTempVoList(pcsMaxTempVos);
pcsMaxTempList.add(pcdData);
}
siteMonitorRuningInfoVo.setPcsMaxTempList(pcsMaxTempList);
int pointCount = pcsMaxTempList.stream()
.filter(item -> item != null && item.getMaxTempVoList() != null)
.mapToInt(item -> item.getMaxTempVoList().size())
.sum();
log.info("{} pcsMaxTemp, siteId={}, startDate={}, endDate={}, deviceCount={}, pointCount={}",
RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pcsMaxTempList.size(), pointCount);
return siteMonitorRuningInfoVo;
}
private List<PcsMaxTempList> fullFillMaxTempData(List<PcsMaxTempList> pcsMaxTempList, List<LocalDateTime> targetHours) {
@ -415,14 +462,32 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
@Override
public SiteMonitorRuningInfoVo getRunningGraphBatterySoc(RunningGraphRequest request) {
SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
if (!StringUtils.isEmpty(request.getSiteId())) {
// // 时间暂定今日+昨日
// Date today = new Date();
// Date yesterday = DateUtils.addDays(today, -1);
//电池平均soclist
List<BatteryAveSOCVo> batteryAveSOCList = emsBatteryStackMapper.getAveSocList(request.getSiteId(), request.getStartDate(), request.getEndDate());
siteMonitorRuningInfoVo.setBatteryAveSOCList(batteryAveSOCList);
if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
log.info("{} batteryAveSoc skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
return siteMonitorRuningInfoVo;
}
Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
Date startDate = dateRange[0];
Date endDate = dateRange[1];
Map<String, EmsSiteMonitorPointMatch> mappingByField = getMonitorPointMappingByField(request.getSiteId());
String pointId = firstNonBlankPoint(mappingByField, FIELD_CURVE_BATTERY_AVE_SOC, FIELD_SOC);
List<InfluxPointDataWriter.PointValue> values = queryInfluxPointValues(request.getSiteId(), pointId, startDate, endDate);
List<BatteryAveSOCVo> batteryAveSOCList = new ArrayList<>();
for (InfluxPointDataWriter.PointValue value : values) {
if (value == null || value.getDataTime() == null || value.getPointValue() == null) {
continue;
}
Date dataTime = value.getDataTime();
BatteryAveSOCVo vo = new BatteryAveSOCVo();
vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
vo.setBatterySOC(value.getPointValue());
batteryAveSOCList.add(vo);
}
siteMonitorRuningInfoVo.setBatteryAveSOCList(batteryAveSOCList);
int pointCount = CollectionUtils.isEmpty(batteryAveSOCList) ? 0 : batteryAveSOCList.size();
log.info("{} batteryAveSoc, siteId={}, startDate={}, endDate={}, pointId={}, pointCount={}",
RUNNING_GRAPH_DEBUG, request.getSiteId(), startDate, endDate, pointId, pointCount);
return siteMonitorRuningInfoVo;
}
// 获取单站监控实时运行-电池平均温度
@ -430,40 +495,178 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
public SiteMonitorRuningInfoVo getRunningGraphBatteryTemp(RunningGraphRequest request) {
SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
log.info("{} batteryAveTemp skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
return siteMonitorRuningInfoVo;
}
String siteId = request.getSiteId();
Date startDate = request.getStartDate();
Date endDate = request.getEndDate();
//电池平均温度list,优先从电池堆取,电池堆没有的话再从电池簇取
Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
Date startDate = dateRange[0];
Date endDate = dateRange[1];
Map<String, EmsSiteMonitorPointMatch> mappingByField = getMonitorPointMappingByField(siteId);
String pointId = firstNonBlankPoint(mappingByField, FIELD_CURVE_BATTERY_AVE_TEMP, FIELD_HOME_AVG_TEMP);
List<InfluxPointDataWriter.PointValue> values = queryInfluxPointValues(siteId, pointId, startDate, endDate);
List<BatteryAveTempVo> batteryAveTempList = new ArrayList<>();
batteryAveTempList = emsBatteryStackMapper.getBatteryAveTempList(siteId, startDate, endDate);
// 电池堆暂无数据,从电池簇取
if (CollectionUtils.isEmpty(batteryAveTempList)) {
batteryAveTempList = emsBatteryClusterMapper.getBatteryAveTempList(siteId, startDate, endDate);
for (InfluxPointDataWriter.PointValue value : values) {
if (value == null || value.getDataTime() == null || value.getPointValue() == null) {
continue;
}
Date dataTime = value.getDataTime();
BatteryAveTempVo vo = new BatteryAveTempVo();
vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
vo.setBatteryTemp(value.getPointValue());
batteryAveTempList.add(vo);
}
// if (SiteEnum.FX.getCode().equals(siteId)) {
// batteryAveTempList = emsBatteryClusterMapper.getBatteryAveTempList(siteId, startDate, endDate);
// } else if (SiteEnum.DDS.getCode().equals(siteId)) {
// batteryAveTempList = emsBatteryStackMapper.getBatteryAveTempList(siteId, startDate, endDate);
// }
siteMonitorRuningInfoVo.setBatteryAveTempList(batteryAveTempList);
// if (!StringUtils.isEmpty(siteId)) {
// // 时间暂定今日+昨日
// Date today = new Date();
// Date yesterday = DateUtils.addDays(today, -1);
// //电池平均温度list
// List<BatteryAveTempVo> batteryAveTempList = new ArrayList<>();
// if (SiteEnum.FX.getCode().equals(siteId)) {
// batteryAveTempList = emsBatteryClusterMapper.getBatteryAveTempList(siteId, yesterday, today);
// } else if (SiteEnum.DDS.getCode().equals(siteId)) {
// batteryAveTempList = emsBatteryStackMapper.getBatteryAveTempList(siteId, yesterday, today);
// }
// siteMonitorRuningInfoVo.setBatteryAveTempList(batteryAveTempList);
// }
int pointCount = CollectionUtils.isEmpty(batteryAveTempList) ? 0 : batteryAveTempList.size();
log.info("{} batteryAveTemp, siteId={}, startDate={}, endDate={}, pointId={}, pointCount={}",
RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pointId, pointCount);
return siteMonitorRuningInfoVo;
}
private Date[] normalizeRunningGraphDateRange(Date startDate, Date endDate) {
Date normalizedStart = startDate;
Date normalizedEnd = endDate;
if (normalizedStart == null || normalizedEnd == null) {
Date today = DateUtils.getNowDate();
Date yesterday = DateUtils.addDays(today, -1);
normalizedStart = normalizedStart == null ? yesterday : normalizedStart;
normalizedEnd = normalizedEnd == null ? today : normalizedEnd;
}
LocalDate startDay = DateUtils.toLocalDateTime(normalizedStart).toLocalDate();
LocalDate endDay = DateUtils.toLocalDateTime(normalizedEnd).toLocalDate();
Date dayStart = DateUtils.toDate(startDay.atStartOfDay());
Date dayEnd = DateUtils.toDate(endDay.plusDays(1).atStartOfDay().minusNanos(1_000_000));
return new Date[]{dayStart, dayEnd};
}
private Map<String, EmsSiteMonitorPointMatch> getMonitorPointMappingByField(String siteId) {
if (StringUtils.isBlank(siteId)) {
return Collections.emptyMap();
}
List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId.trim());
if (CollectionUtils.isEmpty(mappingList)) {
return Collections.emptyMap();
}
return mappingList.stream()
.filter(item -> item != null && StringUtils.isNotBlank(item.getFieldCode()))
.collect(Collectors.toMap(
item -> item.getFieldCode().trim(),
item -> item,
(a, b) -> b
));
}
private Map<String, EmsSiteMonitorPointMatch> buildMonitorPointMappingByFieldAndDevice(List<EmsSiteMonitorPointMatch> mappingList) {
if (CollectionUtils.isEmpty(mappingList)) {
return Collections.emptyMap();
}
return mappingList.stream()
.filter(item -> item != null && StringUtils.isNotBlank(item.getFieldCode()))
.collect(Collectors.toMap(
item -> buildFieldDeviceKey(item.getFieldCode(), item.getDeviceId()),
item -> item,
(a, b) -> b
));
}
private String buildFieldDeviceKey(String fieldCode, String deviceId) {
return StringUtils.defaultString(fieldCode).trim() + "|" + StringUtils.defaultString(deviceId).trim();
}
private String firstNonBlankPointByDevice(Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
String deviceId, String... fieldCodes) {
if (mappingByFieldAndDevice == null || fieldCodes == null) {
return null;
}
String normalizedDeviceId = StringUtils.defaultString(deviceId).trim();
for (String fieldCode : fieldCodes) {
if (StringUtils.isBlank(fieldCode)) {
continue;
}
EmsSiteMonitorPointMatch exact = mappingByFieldAndDevice.get(buildFieldDeviceKey(fieldCode, normalizedDeviceId));
if (exact != null && StringUtils.isNotBlank(exact.getDataPoint())) {
return exact.getDataPoint().trim();
}
EmsSiteMonitorPointMatch fallback = mappingByFieldAndDevice.get(buildFieldDeviceKey(fieldCode, ""));
if (fallback != null && StringUtils.isNotBlank(fallback.getDataPoint())) {
return fallback.getDataPoint().trim();
}
}
return null;
}
private List<DeviceMeta> getDeviceMetaListByCategory(String siteId, String deviceCategory) {
if (StringUtils.isBlank(siteId) || StringUtils.isBlank(deviceCategory)) {
return Collections.emptyList();
}
List<Map<String, Object>> deviceList = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, deviceCategory);
if (CollectionUtils.isEmpty(deviceList)) {
return Collections.emptyList();
}
List<DeviceMeta> result = new ArrayList<>();
for (Map<String, Object> item : deviceList) {
if (item == null || item.get("id") == null) {
continue;
}
String deviceId = String.valueOf(item.get("id")).trim();
if (StringUtils.isBlank(deviceId)) {
continue;
}
String deviceName = item.get("deviceName") == null ? deviceId : String.valueOf(item.get("deviceName")).trim();
result.add(new DeviceMeta(deviceId, deviceName));
}
result.sort(Comparator.comparing(DeviceMeta::getDeviceName, Comparator.nullsLast(String::compareTo))
.thenComparing(DeviceMeta::getDeviceId, Comparator.nullsLast(String::compareTo)));
return result;
}
private static class DeviceMeta {
private final String deviceId;
private final String deviceName;
private DeviceMeta(String deviceId, String deviceName) {
this.deviceId = deviceId;
this.deviceName = deviceName;
}
public String getDeviceId() {
return deviceId;
}
public String getDeviceName() {
return deviceName;
}
}
private String firstNonBlankPoint(Map<String, EmsSiteMonitorPointMatch> mappingByField, String... fieldCodes) {
if (mappingByField == null || fieldCodes == null) {
return null;
}
for (String fieldCode : fieldCodes) {
if (StringUtils.isBlank(fieldCode)) {
continue;
}
EmsSiteMonitorPointMatch match = mappingByField.get(fieldCode);
if (match != null && StringUtils.isNotBlank(match.getDataPoint())) {
return match.getDataPoint().trim();
}
}
return null;
}
private List<InfluxPointDataWriter.PointValue> queryInfluxPointValues(String siteId, String pointId, Date startDate, Date endDate) {
if (StringUtils.isBlank(siteId) || StringUtils.isBlank(pointId) || startDate == null || endDate == null) {
return Collections.emptyList();
}
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveDataByPointKey(siteId, pointId, startDate, endDate);
if (CollectionUtils.isEmpty(values)) {
return Collections.emptyList();
}
values.sort(Comparator.comparing(InfluxPointDataWriter.PointValue::getDataTime, Comparator.nullsLast(Date::compareTo)));
return values;
}
// 根据site_id获取pcs详细数据+支路数据
@Override
public List<PcsDetailInfoVo> getPcsDetailInfo(String siteId) {
@ -472,17 +675,27 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
if (!StringUtils.isEmpty(siteId)) {
// 获取该设备下所有pcs的id
List<Map<String, Object>> pcsIds = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.PCS.getCode());
List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList);
Map<String, InfluxPointDataWriter.PointValue> latestPointCache = new HashMap<>();
for (Map<String, Object> pcsDevice : pcsIds) {
PcsDetailInfoVo pcsDetailInfoVo = new PcsDetailInfoVo();
pcsDetailInfoVo.setDeviceName(pcsDevice.get("deviceName").toString());
pcsDetailInfoVo.setCommunicationStatus(pcsDevice.get("communicationStatus") == null ?
"" :pcsDevice.get("communicationStatus").toString());
// 从redis取pcs单个详细数据
String pcsId = pcsDevice.get("id").toString();
EmsPcsData pcsData = redisCache.getCacheObject(RedisKeyConstants.PCS +siteId+"_"+pcsId);
if (pcsData != null) {
BeanUtils.copyProperties(pcsData, pcsDetailInfoVo);
String pcsId = String.valueOf(pcsDevice.get(DEVICE_INFO_ID));
pcsDetailInfoVo.setSiteId(siteId);
pcsDetailInfoVo.setDeviceId(pcsId);
pcsDetailInfoVo.setDeviceName(String.valueOf(pcsDevice.get(DEVICE_INFO_NAME)));
fillPcsDetailByLatestPointMapping(siteId, pcsId, pcsDetailInfoVo, mappingByFieldAndDevice, latestPointCache);
if (StringUtils.isBlank(pcsDetailInfoVo.getCommunicationStatus())) {
pcsDetailInfoVo.setCommunicationStatus(pcsDevice.get(DEVICE_INFO_COMM_STATUS) == null
? ""
: pcsDevice.get(DEVICE_INFO_COMM_STATUS).toString());
}
if (StringUtils.isBlank(pcsDetailInfoVo.getDeviceStatus())) {
pcsDetailInfoVo.setDeviceStatus(pcsDevice.get(DEVICE_INFO_DEVICE_STATUS) == null
? ""
: pcsDevice.get(DEVICE_INFO_DEVICE_STATUS).toString());
}
// 支路信息数据
List<PcsBranchInfo> pcsBranchInfoList = new ArrayList<>();
@ -493,7 +706,6 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
// // 告警设备点位个数
// int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, pcsId, DeviceCategory.PCS.getCode());
pcsDetailInfoVo.setAlarmNum(alarmNum);
pcsDetailInfoVo.setDeviceStatus(pcsDevice.get("deviceStatus") == null ? "" : pcsDevice.get("deviceStatus").toString());
// 处理枚举匹配字段
devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.PCS.getCode(), pcsDetailInfoVo);
@ -504,6 +716,114 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
return pcsDetailInfoVoList;
}
private void fillPcsDetailByLatestPointMapping(String siteId,
String deviceId,
PcsDetailInfoVo target,
Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
Map<String, InfluxPointDataWriter.PointValue> latestPointCache) {
if (StringUtils.isAnyBlank(siteId, deviceId) || target == null || mappingByFieldAndDevice == null) {
return;
}
BeanWrapper beanWrapper = new BeanWrapperImpl(target);
Date latestDataTime = null;
for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) {
if (pd == null || StringUtils.isBlank(pd.getName()) || pd.getWriteMethod() == null) {
continue;
}
String fieldCode = pd.getName();
if (PCS_DETAIL_META_FIELDS.contains(fieldCode)) {
continue;
}
EmsSiteMonitorPointMatch pointMatch = resolvePointMatchByFieldAndDevice(mappingByFieldAndDevice, fieldCode, deviceId);
if (pointMatch == null) {
continue;
}
Object fieldValue;
Date valueTime = null;
if (USE_FIXED_DISPLAY_YES == (pointMatch.getUseFixedDisplay() == null ? 0 : pointMatch.getUseFixedDisplay())
&& StringUtils.isNotBlank(pointMatch.getFixedDataPoint())) {
fieldValue = pointMatch.getFixedDataPoint().trim();
} else {
InfluxPointDataWriter.PointValue latestValue = getLatestPointValueByPointId(siteId, pointMatch.getDataPoint(), latestPointCache);
if (latestValue == null || latestValue.getPointValue() == null) {
continue;
}
fieldValue = latestValue.getPointValue();
valueTime = latestValue.getDataTime();
}
Object convertedValue = convertFieldValueByType(fieldValue, pd.getPropertyType());
if (convertedValue == null) {
continue;
}
beanWrapper.setPropertyValue(fieldCode, convertedValue);
if (valueTime != null && (latestDataTime == null || valueTime.after(latestDataTime))) {
latestDataTime = valueTime;
}
}
if (latestDataTime != null) {
target.setDataUpdateTime(latestDataTime);
}
}
private EmsSiteMonitorPointMatch resolvePointMatchByFieldAndDevice(Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
String fieldCode,
String deviceId) {
if (mappingByFieldAndDevice == null || StringUtils.isBlank(fieldCode)) {
return null;
}
String normalizedField = fieldCode.trim();
String normalizedDeviceId = StringUtils.defaultString(deviceId).trim();
EmsSiteMonitorPointMatch exact = mappingByFieldAndDevice.get(buildFieldDeviceKey(normalizedField, normalizedDeviceId));
if (exact != null) {
return exact;
}
return mappingByFieldAndDevice.get(buildFieldDeviceKey(normalizedField, ""));
}
private InfluxPointDataWriter.PointValue getLatestPointValueByPointId(String siteId,
String pointId,
Map<String, InfluxPointDataWriter.PointValue> latestPointCache) {
if (StringUtils.isAnyBlank(siteId, pointId)) {
return null;
}
String pointCacheKey = pointId.trim().toUpperCase();
if (latestPointCache.containsKey(pointCacheKey)) {
return latestPointCache.get(pointCacheKey);
}
Date endTime = DateUtils.getNowDate();
Date fastStartTime = DateUtils.addDays(endTime, -1);
InfluxPointDataWriter.PointValue latest = influxPointDataWriter.queryLatestPointValueByPointKey(
siteId.trim(), pointId.trim(), fastStartTime, endTime
);
if (latest == null) {
Date fallbackStartTime = DateUtils.addDays(endTime, -7);
latest = influxPointDataWriter.queryLatestPointValueByPointKey(
siteId.trim(), pointId.trim(), fallbackStartTime, endTime
);
}
latestPointCache.put(pointCacheKey, latest);
return latest;
}
private Object convertFieldValueByType(Object rawValue, Class<?> targetType) {
if (rawValue == null || targetType == null) {
return null;
}
if (BigDecimal.class.equals(targetType)) {
return StringUtils.getBigDecimal(rawValue);
}
if (String.class.equals(targetType)) {
if (rawValue instanceof BigDecimal) {
return ((BigDecimal) rawValue).stripTrailingZeros().toPlainString();
}
return String.valueOf(rawValue);
}
if (Date.class.equals(targetType) && rawValue instanceof Date) {
return rawValue;
}
return null;
}
private void processBranchDataInfo(String siteId, String pcsId, List<PcsBranchInfo> pcsBranchInfoList) {
if (!StringUtils.isEmpty(pcsId)) {
List<EmsPcsBranchData> pcsBranchData = redisCache.getCacheObject(RedisKeyConstants.BRANCH +siteId+"_"+pcsId);

View File

@ -213,7 +213,8 @@
</select>
<select id="getPointNameList" resultType="com.xzzn.ems.domain.vo.GeneralQueryPointOptionVo">
select point_name as pointName,
select point_id as pointId,
point_name as pointName,
data_key as dataKey,
point_desc as pointDesc
from ems_point_config
@ -237,8 +238,8 @@
or point_desc like concat('%', #{pointName}, '%')
)
</if>
group by point_name, data_key, point_desc
order by point_name asc, data_key asc
group by point_id, point_name, data_key, point_desc
order by point_id asc, point_name asc, data_key asc
</select>
<select id="getConfigListForGeneralQuery" resultMap="EmsPointConfigResult">
@ -250,6 +251,14 @@
#{siteId}
</foreach>
</if>
<choose>
<when test="pointIds != null and pointIds.size() > 0">
and point_id in
<foreach collection="pointIds" item="pointId" open="(" separator="," close=")">
#{pointId}
</foreach>
</when>
<otherwise>
<if test="deviceCategory != null and deviceCategory != ''">
and device_category = #{deviceCategory}
</if>
@ -265,6 +274,8 @@
#{deviceId}
</foreach>
</if>
</otherwise>
</choose>
</select>
<select id="selectBySiteIdAndPointIds" resultMap="EmsPointConfigResult">

View File

@ -162,4 +162,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where site_id = #{templateSiteId}
</insert>
<delete id="deleteByScope">
delete from ems_point_enum_match
where site_id = #{siteId}
and device_category = #{deviceCategory}
and match_field = #{matchField}
</delete>
</mapper>

View File

@ -14,27 +14,23 @@
<insert id="upsertHistoryJsonByMinute">
insert into ${tableName} (
site_id, statis_minute, data_json, create_by, create_time, update_by, update_time
site_id, statis_minute, data_json,
hot_soc, hot_total_active_power, hot_total_reactive_power, hot_day_charged_cap, hot_day_dis_charged_cap,
create_by, create_time, update_by, update_time
) values (
#{siteId}, #{statisMinute}, #{dataJson}, #{operName}, now(), #{operName}, now()
#{siteId}, #{statisMinute}, #{dataJson},
#{hotSoc}, #{hotTotalActivePower}, #{hotTotalReactivePower}, #{hotDayChargedCap}, #{hotDayDisChargedCap},
#{operName}, now(), #{operName}, now()
)
on duplicate key update
data_json = values(data_json),
hot_soc = values(hot_soc),
hot_total_active_power = values(hot_total_active_power),
hot_total_reactive_power = values(hot_total_reactive_power),
hot_day_charged_cap = values(hot_day_charged_cap),
hot_day_dis_charged_cap = values(hot_day_dis_charged_cap),
update_by = values(update_by),
update_time = now()
</insert>
<update id="updateHistoryHotColumns">
update ${tableName}
set hot_soc = #{hotSoc},
hot_total_active_power = #{hotTotalActivePower},
hot_total_reactive_power = #{hotTotalReactivePower},
hot_day_charged_cap = #{hotDayChargedCap},
hot_day_dis_charged_cap = #{hotDayDisChargedCap},
update_by = #{operName},
update_time = now()
where site_id = #{siteId}
and statis_minute = #{statisMinute}
</update>
</mapper>

View File

@ -8,6 +8,7 @@
<result property="id" column="id"/>
<result property="siteId" column="site_id"/>
<result property="fieldCode" column="field_code"/>
<result property="deviceId" column="device_id"/>
<result property="dataPoint" column="data_point"/>
<result property="fixedDataPoint" column="fixed_data_point"/>
<result property="useFixedDisplay" column="use_fixed_display"/>
@ -19,7 +20,7 @@
</resultMap>
<select id="selectBySiteId" resultMap="EmsSiteMonitorPointMatchResult">
select id, site_id, field_code, data_point, fixed_data_point, use_fixed_display, create_by, create_time, update_by, update_time, remark
select id, site_id, field_code, device_id, data_point, fixed_data_point, use_fixed_display, create_by, create_time, update_by, update_time, remark
from ems_site_monitor_point_match
where site_id = #{siteId}
order by id asc
@ -32,10 +33,10 @@
<insert id="insertBatch">
insert into ems_site_monitor_point_match
(site_id, field_code, data_point, fixed_data_point, use_fixed_display, create_by, create_time, update_by, update_time)
(site_id, field_code, device_id, data_point, fixed_data_point, use_fixed_display, create_by, create_time, update_by, update_time)
values
<foreach collection="list" item="item" separator=",">
(#{item.siteId}, #{item.fieldCode}, #{item.dataPoint}, #{item.fixedDataPoint}, #{item.useFixedDisplay}, #{item.createBy}, now(), #{item.updateBy}, now())
(#{item.siteId}, #{item.fieldCode}, #{item.deviceId}, #{item.dataPoint}, #{item.fixedDataPoint}, #{item.useFixedDisplay}, #{item.createBy}, now(), #{item.updateBy}, now())
</foreach>
</insert>

View File

@ -134,17 +134,17 @@
</select>
<select id="getAllSiteDeviceList" parameterType="String" resultType="com.xzzn.ems.domain.vo.SiteDeviceListVo">
select es.site_id as siteId,es.site_name as siteName,
select ed.site_id as siteId,
ed.device_id as deviceId,ed.device_name as deviceName,
ed.device_type as deviceType,ed.device_status as deviceStatus,ed.work_status as workStatus,
ed.device_category as deviceCategory,
ed.picture_url as pictureUrl,
ed.id,
ed.parent_id as parentId
from ems_site_setting es INNER JOIN ems_devices_setting ed on es.site_id = ed.site_id
from ems_devices_setting ed
where 1=1
<if test="siteId != null and siteId != ''">
and es.site_id = #{siteId}
and ed.site_id = #{siteId}
</if>
<if test="deviceCategory != null and deviceCategory != ''">
and ed.device_category = #{deviceCategory}
@ -153,18 +153,19 @@
</select>
<select id="getAllSiteDeviceListNoDisp" parameterType="String" resultType="com.xzzn.ems.domain.vo.SiteDeviceListVo">
select es.site_id as siteId,es.site_name as siteName,
select ed.site_id as siteId,
ed.device_id as deviceId,ed.device_name as deviceName,
ed.device_type as deviceType,ed.device_status as deviceStatus,ed.work_status as workStatus,
ed.device_category as deviceCategory,
ed.picture_url as pictureUrl,
ed.id,
ed.parent_id as parentId
from ems_site_setting es INNER JOIN ems_devices_setting ed on es.site_id = ed.site_id and (ed.display_flg is null or ed.display_flg != '1')
from ems_devices_setting ed
where 1 = 1
<if test="siteId != null and siteId != ''">
and es.site_id = #{siteId}
and ed.site_id = #{siteId}
</if>
and (ed.display_flg is null or ed.display_flg != '1')
<if test="deviceCategory != null and deviceCategory != ''">
and ed.device_category = #{deviceCategory}
</if>

View File

@ -14,6 +14,12 @@
<result property="antiReverseUp" column="anti_reverse_up"/>
<result property="antiReversePowerDownPercent" column="anti_reverse_power_down_percent"/>
<result property="antiReverseHardStopThreshold" column="anti_reverse_hard_stop_threshold"/>
<result property="powerSetMultiplier" column="power_set_multiplier"/>
<result property="protectInterveneEnable" column="protect_intervene_enable"/>
<result property="protectL1DeratePercent" column="protect_l1_derate_percent"/>
<result property="protectRecoveryStableSeconds" column="protect_recovery_stable_seconds"/>
<result property="protectL3LatchEnable" column="protect_l3_latch_enable"/>
<result property="protectConflictPolicy" column="protect_conflict_policy"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
@ -31,6 +37,12 @@
anti_reverse_up,
anti_reverse_power_down_percent,
anti_reverse_hard_stop_threshold,
power_set_multiplier,
protect_intervene_enable,
protect_l1_derate_percent,
protect_recovery_stable_seconds,
protect_l3_latch_enable,
protect_conflict_policy,
create_by,
create_time,
update_by,
@ -56,6 +68,12 @@
<if test="antiReverseUp != null">anti_reverse_up,</if>
<if test="antiReversePowerDownPercent != null">anti_reverse_power_down_percent,</if>
<if test="antiReverseHardStopThreshold != null">anti_reverse_hard_stop_threshold,</if>
<if test="powerSetMultiplier != null">power_set_multiplier,</if>
<if test="protectInterveneEnable != null">protect_intervene_enable,</if>
<if test="protectL1DeratePercent != null">protect_l1_derate_percent,</if>
<if test="protectRecoveryStableSeconds != null">protect_recovery_stable_seconds,</if>
<if test="protectL3LatchEnable != null">protect_l3_latch_enable,</if>
<if test="protectConflictPolicy != null and protectConflictPolicy != ''">protect_conflict_policy,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
@ -71,6 +89,12 @@
<if test="antiReverseUp != null">#{antiReverseUp},</if>
<if test="antiReversePowerDownPercent != null">#{antiReversePowerDownPercent},</if>
<if test="antiReverseHardStopThreshold != null">#{antiReverseHardStopThreshold},</if>
<if test="powerSetMultiplier != null">#{powerSetMultiplier},</if>
<if test="protectInterveneEnable != null">#{protectInterveneEnable},</if>
<if test="protectL1DeratePercent != null">#{protectL1DeratePercent},</if>
<if test="protectRecoveryStableSeconds != null">#{protectRecoveryStableSeconds},</if>
<if test="protectL3LatchEnable != null">#{protectL3LatchEnable},</if>
<if test="protectConflictPolicy != null and protectConflictPolicy != ''">#{protectConflictPolicy},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
@ -89,6 +113,12 @@
<if test="antiReverseUp != null">anti_reverse_up = #{antiReverseUp},</if>
<if test="antiReversePowerDownPercent != null">anti_reverse_power_down_percent = #{antiReversePowerDownPercent},</if>
<if test="antiReverseHardStopThreshold != null">anti_reverse_hard_stop_threshold = #{antiReverseHardStopThreshold},</if>
<if test="powerSetMultiplier != null">power_set_multiplier = #{powerSetMultiplier},</if>
<if test="protectInterveneEnable != null">protect_intervene_enable = #{protectInterveneEnable},</if>
<if test="protectL1DeratePercent != null">protect_l1_derate_percent = #{protectL1DeratePercent},</if>
<if test="protectRecoveryStableSeconds != null">protect_recovery_stable_seconds = #{protectRecoveryStableSeconds},</if>
<if test="protectL3LatchEnable != null">protect_l3_latch_enable = #{protectL3LatchEnable},</if>
<if test="protectConflictPolicy != null and protectConflictPolicy != ''">protect_conflict_policy = #{protectConflictPolicy},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>