中央热水项目
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

469 lines
20 KiB

package com.mh.user.job;
import com.mh.user.constants.Constant;
import com.mh.user.entity.CollectionParamsManageEntity;
import com.mh.user.entity.DeviceInstallEntity;
import com.mh.user.entity.GatewayManageEntity;
import com.mh.user.mapper.CollectionParamsManageMapper;
import com.mh.user.mapper.DeviceInstallMapper;
import com.mh.user.mapper.GatewayManageMapper;
import com.mh.user.mapper.NowDataMapper;
import com.mh.user.s7.S7ConnectorUtil;
import com.mh.user.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* S7 PLC定时采集任务
* 支持M、VB、VW、VD等地址类型的读写操作
*
* @author System
* @date 2026-06-23
*/
@Slf4j
@Component
public class S7PlcCollectionJob {
private final GatewayManageMapper gatewayManageMapper;
private final CollectionParamsManageMapper collectionParamsManageMapper;
// 缓存S7连接器,避免频繁创建连接
private static final Map<String, S7ConnectorUtil> connectorCache = new ConcurrentHashMap<>();
private final DeviceInstallMapper deviceInstallMapper;
private final NowDataMapper nowDataMapper;
public S7PlcCollectionJob(GatewayManageMapper gatewayManageMapper,
CollectionParamsManageMapper collectionParamsManageMapper, DeviceInstallMapper deviceInstallMapper, NowDataMapper nowDataMapper, NowDataMapper nowDataMapper1) {
this.gatewayManageMapper = gatewayManageMapper;
this.collectionParamsManageMapper = collectionParamsManageMapper;
this.deviceInstallMapper = deviceInstallMapper;
this.nowDataMapper = nowDataMapper1;
}
/**
* 定时采集S7 PLC数据
* 每5分钟执行一次,可根据实际需求调整
* 优先处理手动操作:如果Constant.WEB_PLC_FLAG为true,则跳过本次采集
*/
@Scheduled(cron = "0 0/5 * * * ?")
public void collectS7Data() {
log.info("------S7 PLC定时采集开始>>>>Constant.FLAG=={}------", Constant.PLC_FLAG);
try {
// 检查是否有手动操作正在进行
if (Constant.PLC_FLAG || Constant.WEB_PLC_FLAG) {
log.info("存在手动操作,跳过本次S7 PLC采集");
return;
}
Constant.PLC_FLAG = true;
// 查询所有在线的S7网关
List<GatewayManageEntity> s7Gateways = gatewayManageMapper.queryS7Gateways();
if (s7Gateways == null || s7Gateways.isEmpty()) {
log.info("未找到在线的S7网关");
return;
}
log.info("找到{}个在线S7网关", s7Gateways.size());
// 遍历每个S7网关
for (GatewayManageEntity gateway : s7Gateways) {
try {
processGateway(gateway);
} catch (Exception e) {
log.error("处理S7网关异常: gatewayName={}, dataCom={}",
gateway.getGatewayName(), gateway.getDataCom(), e);
}
}
} catch (Exception e) {
log.error("S7 PLC定时采集异常", e);
} finally {
Constant.PLC_FLAG = false;
log.info("------S7 PLC定时采集结束>>>>Constant.FLAG=={}------", Constant.PLC_FLAG);
}
}
/**
* 处理单个S7网关的数据采集
*/
private void processGateway(GatewayManageEntity gateway) {
String dataCom = gateway.getDataCom();
if (dataCom == null || dataCom.isEmpty()) {
log.warn("网关dataCom为空: {}", gateway.getGatewayName());
return;
}
// 获取或创建S7连接器
S7ConnectorUtil connector = getOrCreateConnector(gateway);
if (connector == null) {
log.error("无法创建S7连接器: {}", gateway.getGatewayName());
return;
}
// 查询该网关对应的采集参数
List<CollectionParamsManageEntity> params = collectionParamsManageMapper.selectCPMByDataCom(dataCom);
if (params == null || params.isEmpty()) {
log.info("网关{}没有配置采集参数", gateway.getGatewayName());
return;
}
log.info("开始采集网关{}的{}个点位", gateway.getGatewayName(), params.size());
// 过滤掉重复的采集参数
params = params.stream().distinct().collect(Collectors.toList());
String dateStr = DateUtil.dateToString(new Date(), "yyyy-MM-dd HH:mm:ss");
// 遍历采集参数并读取数据
for (CollectionParamsManageEntity param : params) {
try {
// 再次检查是否有手动操作
if (Constant.WEB_PLC_FLAG) {
log.info("检测到手动操作,中断采集");
break;
}
readAndSaveData(connector, param, dateStr);
} catch (Exception e) {
log.error("采集点位异常: registerAddr={}, otherName={}",
param.getRegisterAddr(), param.getOtherName(), e);
}
}
// 遍历完全之后更新回水状态,因为有多箱和单箱电磁阀
// params遍历得出多少个buildingId分组
List<Long> buildingIds = params.stream().map(CollectionParamsManageEntity::getBuildingId).distinct().collect(Collectors.toList());
for (Long buildingId : buildingIds) {
List<Map<String, Object>> backWaterStates = collectionParamsManageMapper.selectBackWaterState(buildingId);
// map值有cur_value,cur_time,通过stream判断cur_time是否是当前时间,然后cur_value如果存在一天记录等于1的,back_water_state=运行,否则back_water_state=不运行
backWaterStates.forEach(backWaterState -> {
if (backWaterState.get("cur_time").toString().substring(0, 10).equals(dateStr.substring(0, 10))) {
if (backWaterState.get("cur_value").equals(1)) {
nowDataMapper.updateBackWaterState(buildingId, "运行");
} else {
nowDataMapper.updateBackWaterState(buildingId, "不运行");
}
}
});
}
}
/**
* 读取并保存数据
*/
private void readAndSaveData(S7ConnectorUtil connector,
CollectionParamsManageEntity param,
String dateStr) {
String registerAddr = param.getRegisterAddr();
if (registerAddr == null || registerAddr.isEmpty()) {
log.warn("点位寄存器地址为空: id={}", param.getId());
return;
}
// 读取数据
Object value = connector.readData(registerAddr);
DeviceInstallEntity deviceInstallEntity = deviceInstallMapper.selectDeviceById(param.getDeviceInstallId());
if (value == null) {
log.warn("读取数据为空: registerAddr={}", registerAddr);
// 更新deviceInstall离线
deviceInstallMapper.updateNotOnlineById(param.getDeviceInstallId());
// 更新now_data离线
nowDataMapper.updateRunState(deviceInstallEntity.getBuildingId(),
deviceInstallEntity.getDeviceAddr(),
"2", deviceInstallEntity.getDeviceName());
// 在判断设备类型,如果是供水泵,up_water_state=运行
// 如果是补水电磁阀开,use_water_state=运行,
// 如果是单箱电磁阀或者多箱电磁阀开,back_water_state=运行
if (deviceInstallEntity.getDeviceType().equals("供水泵")) {
nowDataMapper.updateUpWaterState(deviceInstallEntity.getBuildingId(),
deviceInstallEntity.getDeviceAddr(),
"2", deviceInstallEntity.getDeviceName());
} else if (deviceInstallEntity.getDeviceType().equals("补水电磁阀")) {
nowDataMapper.updateUseWaterState(deviceInstallEntity.getBuildingId(),
deviceInstallEntity.getDeviceAddr(),
"2", deviceInstallEntity.getDeviceName());
}
return;
}
// 转换值为BigDecimal
BigDecimal curValue;
try {
// BigDecimal 构造函数可以直接处理 String 和 Number.toString()
curValue = new BigDecimal(value.toString());
} catch (NumberFormatException e) {
log.warn("无法转换为数字: registerAddr={}, value={}", "", value);
return;
}
// 应用倍率和初始值
if (param.getMtRatio() != null && param.getMtRatio() != 1) {
curValue = curValue.multiply(new BigDecimal(param.getMtRatio()));
}
if (param.getMtInitValue() != null) {
curValue = curValue.add(param.getMtInitValue());
}
// 应用小数点位数
if (param.getDigits() != null && param.getDigits() > 0) {
curValue = curValue.setScale(param.getDigits(), BigDecimal.ROUND_HALF_UP);
}
// 更新数据库
collectionParamsManageMapper.updateCollectionParamsManage(
param.getDeviceInstallId().intValue(),
registerAddr,
curValue.toString(),
dateStr,
param.getBuildingId() != null ? param.getBuildingId().toString() : null
);
// 在同步更新device_install表
deviceInstallMapper.updateLastValueByOtherParam(param.getDeviceInstallId(), curValue.toString());
if (deviceInstallEntity != null) {
// 更新设备安装表中的now_date字段,根据param.getParamTypeId()的值进行判断 // 在对now_date进行更新
// 查询当前点位是否是运行状态、压力、液位、液位设置、回水温度、故障状态
log.error("进入nowData设置==>{}", param.toString());
switch(param.getParamTypeId()) {
case 2: // 运行状态
nowDataMapper.updateRunState(deviceInstallEntity.getBuildingId(),
deviceInstallEntity.getDeviceAddr(),
String.valueOf(curValue.intValue()), deviceInstallEntity.getDeviceName());
// 在判断设备类型,如果是供水泵,up_water_state=运行
// 如果是补水电磁阀开,use_water_state=运行,
// 如果是单箱电磁阀或者多箱电磁阀开,back_water_state=运行
if (deviceInstallEntity.getDeviceType().equals("供水泵")) {
nowDataMapper.updateUpWaterState(deviceInstallEntity.getBuildingId(),
deviceInstallEntity.getDeviceAddr(),
String.valueOf(curValue.intValue()), deviceInstallEntity.getDeviceName());
} else if (deviceInstallEntity.getDeviceType().equals("补水电磁阀")) {
nowDataMapper.updateUseWaterState(deviceInstallEntity.getBuildingId(),
deviceInstallEntity.getDeviceAddr(),
String.valueOf(curValue.intValue()), deviceInstallEntity.getDeviceName());
}
break;
case 5: // 压力
if (param.getOtherName().contains("供水压力")) {
nowDataMapper.updatePressure(deviceInstallEntity.getBuildingId(),
null,
curValue.setScale(1, RoundingMode.HALF_UP).toString(), null);
}
break;
case 31: // 液位
if (param.getOtherName().contains("单箱") && deviceInstallEntity.getIsSingleBox() == 1) {
// 更新单箱液位
nowDataMapper.updateBoxLevel(deviceInstallEntity.getBuildingId(),
null,
curValue.setScale(1, RoundingMode.HALF_UP).toString(),
null, 1);
} else {
// 获取多箱的液位
nowDataMapper.updateBoxLevel(deviceInstallEntity.getBuildingId(),
null,
curValue.setScale(1, RoundingMode.HALF_UP).toString(),
null, 0);
}
break;
case 26: // 液位设置
if (param.getOtherName().contains("单箱液位") && param.getOtherName().contains("上限") && deviceInstallEntity.getIsSingleBox() == 1) {
// 更新单箱液位
nowDataMapper.updateBoxLevelSet(deviceInstallEntity.getBuildingId(),
null,
curValue.setScale(1, RoundingMode.HALF_UP).toString(),
null, 1);
} if (param.getOtherName().contains("多箱液位") && param.getOtherName().contains("上限") && deviceInstallEntity.getIsSingleBox() == 0) {
// 获取多箱的液位
nowDataMapper.updateBoxLevelSet(deviceInstallEntity.getBuildingId(),
null,
curValue.setScale(1, RoundingMode.HALF_UP).toString(),
null, 0);
}
break;
case 32: // 压力设置
nowDataMapper.updatePressureSet(deviceInstallEntity.getBuildingId(),
null,
curValue.setScale(1, RoundingMode.HALF_UP).toString(), null);
break;
case 12: // 回水温度
nowDataMapper.updateBackWaterTemp(deviceInstallEntity.getBuildingId(),
null,
curValue.setScale(1, RoundingMode.HALF_UP).toString(), null);
break;
case 3: // 故障状态
nowDataMapper.updatePressureSet(deviceInstallEntity.getBuildingId(),
deviceInstallEntity.getDeviceAddr(),
curValue.intValue() == 0 ? "正常" : "故障", deviceInstallEntity.getDeviceName());
break;
case 41: // 回水阀控制模式:0单箱,1多箱
nowDataMapper.updateBackControlModel(deviceInstallEntity.getBuildingId(),
null,
String.valueOf(curValue.intValue()), null);
break;
}
// 根据deviceInstall查询对应的deviceInstall表数据
// 根据查询出来的deviceInstall表数据,根据device_addr和device_name值进行更新
}
log.debug("采集成功: registerAddr={}, value={}, otherName={}",
registerAddr, curValue, param.getOtherName());
}
/**
* 获取或创建S7连接器
*/
private S7ConnectorUtil getOrCreateConnector(GatewayManageEntity gateway) {
String cacheKey = gateway.getDataCom();
// 从缓存中获取
S7ConnectorUtil connector = connectorCache.get(cacheKey);
if (connector != null) {
return connector;
}
// 创建新连接器
try {
String ipAddress = gateway.getGatewayIP();
if (ipAddress == null || ipAddress.isEmpty()) {
log.error("网关IP地址为空: {}", gateway.getGatewayName());
return null;
}
// 解析rack和slot,默认为0和1
int rack = 0;
int slot = 1;
if (gateway.getGatewayPort() != null && !gateway.getGatewayPort().isEmpty()) {
try {
String[] parts = gateway.getGatewayPort().split(",");
if (parts.length >= 2) {
rack = Integer.parseInt(parts[0].trim());
slot = Integer.parseInt(parts[1].trim());
}
} catch (NumberFormatException e) {
log.warn("解析rack/slot失败,使用默认值: {}", gateway.getGatewayPort());
}
}
connector = new S7ConnectorUtil(ipAddress, rack, slot);
connectorCache.put(cacheKey, connector);
log.info("创建S7连接器成功: IP={}, rack={}, slot={}", ipAddress, rack, slot);
return connector;
} catch (Exception e) {
log.error("创建S7连接器失败: {}", gateway.getGatewayName(), e);
return null;
}
}
/**
* 手动写入数据到PLC(供Controller调用)
*
* @param cpmId 采集参数ID
* @param value 要写入的值
* @return 是否成功
*/
public boolean writeData(Long cpmId, Object value) {
try {
// 查询采集参数
CollectionParamsManageEntity param = collectionParamsManageMapper.selectById(cpmId.toString());
if (param == null) {
log.error("采集参数不存在: cpmId={}", cpmId);
return false;
}
// 查询网关信息
String dataCom = getDataComByDeviceId(param.getDeviceInstallId());
if (dataCom == null) {
log.error("无法获取设备对应的dataCom: deviceInstallId={}", param.getDeviceInstallId());
return false;
}
GatewayManageEntity gateway = getGatewayByDataCom(dataCom);
if (gateway == null) {
log.error("无法获取网关信息: dataCom={}", dataCom);
return false;
}
// 获取连接器
S7ConnectorUtil connector = getOrCreateConnector(gateway);
if (connector == null) {
log.error("无法获取S7连接器");
return false;
}
// 写入数据
connector.writeData(param.getRegisterAddr(), value);
// 更新数据库
String dateStr = DateUtil.dateToString(new Date(), "yyyy-MM-dd HH:mm:ss");
// BigDecimal curValue;
// if (value instanceof Number) {
// curValue = new BigDecimal(value.toString());
// } else {
// curValue = new BigDecimal(value.toString());
// }
collectionParamsManageMapper.updateCollectionParamsManageById(
cpmId,
value.toString(),
dateStr
);
log.info("手动写入成功: cpmId={}, registerAddr={}, value={}", cpmId, param.getRegisterAddr(), value);
return true;
} catch (Exception e) {
log.error("手动写入失败: cpmId={}", cpmId, e);
return false;
}
}
/**
* 根据deviceInstallId获取dataCom
*/
private String getDataComByDeviceId(Long deviceInstallId) {
return gatewayManageMapper.getDataComByDeviceInstallId(deviceInstallId);
}
/**
* 根据dataCom获取网关信息
*/
private GatewayManageEntity getGatewayByDataCom(String dataCom) {
List<GatewayManageEntity> gateways = gatewayManageMapper.queryS7Gateways();
if (gateways != null) {
for (GatewayManageEntity gw : gateways) {
if (dataCom.equals(gw.getDataCom())) {
return gw;
}
}
}
return null;
}
/**
* 清理连接器缓存(可选,用于重启或维护)
*/
public void clearConnectorCache() {
for (Map.Entry<String, S7ConnectorUtil> entry : connectorCache.entrySet()) {
try {
entry.getValue().disconnect();
} catch (Exception e) {
log.error("断开S7连接异常: {}", entry.getKey(), e);
}
}
connectorCache.clear();
log.info("S7连接器缓存已清理");
}
}