添加大车、大理片笼以及多设备串行/并行执行写入基础逻辑
28个文件已修改
19个文件已添加
25个文件已删除
| | |
| | | } |
| | | |
| | | private String resolveOperator() { |
| | | // TODO: ä¹å坿¥å
¥ç»å½ä¸ä¸æï¼è¿é临æ¶åé为 system |
| | | return "system"; |
| | | // 注æï¼è¿éå¯ä»¥æ¥å
¥Spring Securityæå
¶ä»è®¤è¯æ¡æ¶è·åå½åç»å½ç¨æ· |
| | | // ä¾å¦ï¼SecurityContextHolder.getContext().getAuthentication().getName() |
| | | // å½åææ¶ä½¿ç¨systemä½ä¸ºé»è®¤å¼ |
| | | try { |
| | | // å¯ä»¥å°è¯ä»è¯·æ±ä¸ä¸æè·åç¨æ·ä¿¡æ¯ |
| | | // è¿éææ¶è¿åsystemï¼åç»å¯ä»¥æ©å± |
| | | return "system"; |
| | | } catch (Exception e) { |
| | | return "system"; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | package com.mes.device.controller; |
| | | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.mes.device.entity.DeviceGroupConfig; |
| | | import com.mes.device.request.DeviceGroupRequest; |
| | | import com.mes.device.service.DeviceGroupConfigService; |
| | |
| | | |
| | | @Autowired |
| | | private DeviceGroupRelationService deviceGroupRelationService; |
| | | |
| | | @Resource |
| | | private ObjectMapper objectMapper; |
| | | |
| | | /** |
| | | * å建设å¤ç» |
| | |
| | | public Result<DeviceGroupConfig> createGroup( |
| | | @Valid @RequestBody DeviceGroupRequest request) { |
| | | try { |
| | | DeviceGroupConfig groupConfig = (DeviceGroupConfig) request.getGroupConfig(); |
| | | DeviceGroupConfig groupConfig = convertToDeviceGroupConfig(request.getGroupConfig()); |
| | | if (groupConfig == null) { |
| | | return Result.error("设å¤ç»é
置信æ¯ä¸è½ä¸ºç©º"); |
| | | } |
| | | boolean success = deviceGroupConfigService.createDeviceGroup(groupConfig); |
| | | if (success) { |
| | | // å建æååï¼éæ°è·å设å¤ç»å¯¹è±¡ |
| | | DeviceGroupConfig created = deviceGroupConfigService.getDeviceGroupByCode(groupConfig.getGroupCode()); |
| | | return Result.success(created); |
| | | } else { |
| | | return Result.error(); |
| | | return Result.error("å建设å¤ç»å¤±è´¥"); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("å建设å¤ç»å¤±è´¥", e); |
| | | return Result.error(); |
| | | return Result.error("å建设å¤ç»å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | |
| | | public Result<DeviceGroupConfig> updateGroup( |
| | | @Valid @RequestBody DeviceGroupRequest request) { |
| | | try { |
| | | DeviceGroupConfig groupConfig = (DeviceGroupConfig) request.getGroupConfig(); |
| | | if (request.getGroupId() == null) { |
| | | return Result.error("设å¤ç»IDä¸è½ä¸ºç©º"); |
| | | } |
| | | DeviceGroupConfig groupConfig = convertToDeviceGroupConfig(request.getGroupConfig()); |
| | | if (groupConfig == null) { |
| | | return Result.error("设å¤ç»é
置信æ¯ä¸è½ä¸ºç©º"); |
| | | } |
| | | groupConfig.setId(request.getGroupId()); |
| | | boolean success = deviceGroupConfigService.updateDeviceGroup(groupConfig); |
| | | if (success) { |
| | |
| | | DeviceGroupConfig updated = deviceGroupConfigService.getDeviceGroupByCode(groupConfig.getGroupCode()); |
| | | return Result.success(updated); |
| | | } else { |
| | | return Result.error(); |
| | | return Result.error("æ´æ°è®¾å¤ç»é
置失败"); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("æ´æ°è®¾å¤ç»é
置失败", e); |
| | | return Result.error(); |
| | | return Result.error("æ´æ°è®¾å¤ç»é
置失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | |
| | | return Result.error(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * å°Object转æ¢ä¸ºDeviceGroupConfig |
| | | * |
| | | * @param obj å¾
转æ¢ç对象 |
| | | * @return DeviceGroupConfig对象ï¼å¦æè½¬æ¢å¤±è´¥è¿ånull |
| | | */ |
| | | private DeviceGroupConfig convertToDeviceGroupConfig(Object obj) { |
| | | if (obj == null) { |
| | | return null; |
| | | } |
| | | |
| | | // å¦æå·²ç»æ¯DeviceGroupConfigç±»åï¼ç´æ¥è¿å |
| | | if (obj instanceof DeviceGroupConfig) { |
| | | return (DeviceGroupConfig) obj; |
| | | } |
| | | |
| | | // 妿æ¯Mapç±»åï¼ä½¿ç¨ObjectMapperè½¬æ¢ |
| | | if (obj instanceof Map) { |
| | | try { |
| | | return objectMapper.convertValue(obj, DeviceGroupConfig.class); |
| | | } catch (Exception e) { |
| | | log.error("转æ¢Mapå°DeviceGroupConfig失败", e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | // å
¶ä»ç±»åï¼å°è¯ä½¿ç¨ObjectMapperè½¬æ¢ |
| | | try { |
| | | return objectMapper.convertValue(obj, DeviceGroupConfig.class); |
| | | } catch (Exception e) { |
| | | log.error("转æ¢Objectå°DeviceGroupConfig失败: obj={}", obj, e); |
| | | return null; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | package com.mes.device.controller; |
| | | |
| | | import com.mes.device.entity.DeviceStatus; |
| | | import com.mes.device.service.DeviceStatusService; |
| | | import com.mes.vo.Result; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import io.swagger.annotations.ApiParam; |
| | | import lombok.Data; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.validation.Valid; |
| | | import javax.validation.constraints.NotEmpty; |
| | | import javax.validation.constraints.NotNull; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 设å¤ç¶æç®¡çæ§å¶å¨ |
| | | */ |
| | | @RestController |
| | | @RequestMapping("device/status") |
| | | @Api(tags = "设å¤ç¶æç®¡ç") |
| | | @Validated |
| | | @RequiredArgsConstructor |
| | | public class DeviceStatusController { |
| | | |
| | | private final DeviceStatusService deviceStatusService; |
| | | |
| | | @PostMapping("/update") |
| | | @ApiOperation("æ´æ°è®¾å¤å¨çº¿ç¶æ") |
| | | public Result<Boolean> updateDeviceOnlineStatus( |
| | | @Valid @RequestBody DeviceStatusUpdateRequest request) { |
| | | try { |
| | | boolean success = deviceStatusService.updateDeviceOnlineStatus( |
| | | request.getDeviceId(), |
| | | request.getStatus() |
| | | ); |
| | | if (success) { |
| | | return Result.success(true); |
| | | } else { |
| | | return Result.error("æ´æ°è®¾å¤å¨çº¿ç¶æå¤±è´¥"); |
| | | } |
| | | } catch (Exception e) { |
| | | return Result.error("æ´æ°è®¾å¤å¨çº¿ç¶æå¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @PostMapping("/batch-update") |
| | | @ApiOperation("æ¹éæ´æ°è®¾å¤å¨çº¿ç¶æ") |
| | | public Result<Boolean> batchUpdateDeviceOnlineStatus( |
| | | @Valid @RequestBody DeviceStatusBatchUpdateRequest request) { |
| | | try { |
| | | boolean success = deviceStatusService.batchUpdateDeviceOnlineStatus( |
| | | request.getDeviceIds(), |
| | | request.getStatus() |
| | | ); |
| | | if (success) { |
| | | return Result.success(true); |
| | | } else { |
| | | return Result.error("æ¹éæ´æ°è®¾å¤å¨çº¿ç¶æå¤±è´¥"); |
| | | } |
| | | } catch (Exception e) { |
| | | return Result.error("æ¹éæ´æ°è®¾å¤å¨çº¿ç¶æå¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @GetMapping("/latest/{deviceId}") |
| | | @ApiOperation("è·åè®¾å¤ææ°ç¶æ") |
| | | public Result<DeviceStatus> getLatestStatus( |
| | | @ApiParam(value = "设å¤é
ç½®ID", required = true) @PathVariable Long deviceId) { |
| | | try { |
| | | DeviceStatus status = deviceStatusService.getLatestByDeviceConfigId(deviceId); |
| | | return Result.success(status); |
| | | } catch (Exception e) { |
| | | return Result.error("è·å设å¤ç¶æå¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @PostMapping("/heartbeat") |
| | | @ApiOperation("è®°å½è®¾å¤å¿è·³") |
| | | public Result<Boolean> recordHeartbeat( |
| | | @Valid @RequestBody DeviceHeartbeatRequest request) { |
| | | try { |
| | | boolean success = deviceStatusService.recordHeartbeat( |
| | | request.getDeviceId(), |
| | | request.getStatus() |
| | | ); |
| | | if (success) { |
| | | return Result.success(true); |
| | | } else { |
| | | return Result.error("è®°å½è®¾å¤å¿è·³å¤±è´¥"); |
| | | } |
| | | } catch (Exception e) { |
| | | return Result.error("è®°å½è®¾å¤å¿è·³å¤±è´¥: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 设å¤ç¶ææ´æ°è¯·æ± |
| | | */ |
| | | @Data |
| | | public static class DeviceStatusUpdateRequest { |
| | | @NotNull(message = "设å¤IDä¸è½ä¸ºç©º") |
| | | @ApiParam(value = "设å¤é
ç½®ID", required = true) |
| | | private Long deviceId; |
| | | |
| | | @NotEmpty(message = "设å¤ç¶æä¸è½ä¸ºç©º") |
| | | @ApiParam(value = "设å¤ç¶æï¼ONLINE/OFFLINE/BUSY/ERROR/MAINTENANCEï¼", required = true) |
| | | private String status; |
| | | } |
| | | |
| | | /** |
| | | * æ¹é设å¤ç¶ææ´æ°è¯·æ± |
| | | */ |
| | | @Data |
| | | public static class DeviceStatusBatchUpdateRequest { |
| | | @NotEmpty(message = "设å¤IDå表ä¸è½ä¸ºç©º") |
| | | @ApiParam(value = "设å¤é
ç½®IDå表", required = true) |
| | | private List<Long> deviceIds; |
| | | |
| | | @NotEmpty(message = "设å¤ç¶æä¸è½ä¸ºç©º") |
| | | @ApiParam(value = "设å¤ç¶æï¼ONLINE/OFFLINE/BUSY/ERROR/MAINTENANCEï¼", required = true) |
| | | private String status; |
| | | } |
| | | |
| | | /** |
| | | * 设å¤å¿è·³è¯·æ± |
| | | */ |
| | | @Data |
| | | public static class DeviceHeartbeatRequest { |
| | | @NotEmpty(message = "设å¤IDä¸è½ä¸ºç©º") |
| | | @ApiParam(value = "设å¤IDï¼device_config.device_idï¼", required = true) |
| | | private String deviceId; |
| | | |
| | | @ApiParam(value = "设å¤ç¶æï¼å¯éï¼é»è®¤ä¸ºONLINEï¼") |
| | | private String status; |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.mes.device.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * 设å¤ç¶æå®ä½ç±» |
| | | * å¯¹åºæ°æ®åºè¡¨ï¼device_status |
| | | */ |
| | | @Data |
| | | @TableName("device_status") |
| | | @ApiModel(value = "设å¤ç¶æä¿¡æ¯") |
| | | public class DeviceStatus { |
| | | |
| | | @ApiModelProperty(value = "è®°å½ID") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty(value = "设å¤IDï¼device_config.device_idï¼", example = "DEVICE_001") |
| | | @TableField("device_id") |
| | | private String deviceId; |
| | | |
| | | @ApiModelProperty(value = "å
³èä»»å¡ID", example = "TASK_001") |
| | | @TableField("task_id") |
| | | private String taskId; |
| | | |
| | | @ApiModelProperty(value = "设å¤ç¶æ", example = "ONLINE/OFFLINE/BUSY/ERROR/MAINTENANCE") |
| | | @TableField("status") |
| | | private String status; |
| | | |
| | | @ApiModelProperty(value = "æåå¿è·³æ¶é´") |
| | | @TableField("last_heartbeat") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | private Date lastHeartbeat; |
| | | |
| | | @ApiModelProperty(value = "CPU使ç¨ç(%)") |
| | | @TableField("cpu_usage") |
| | | private BigDecimal cpuUsage; |
| | | |
| | | @ApiModelProperty(value = "å
å使ç¨ç(%)") |
| | | @TableField("memory_usage") |
| | | private BigDecimal memoryUsage; |
| | | |
| | | @ApiModelProperty(value = "PLCè¿æ¥ç¶æ", example = "CONNECTED/DISCONNECTED/ERROR") |
| | | @TableField("plc_connection_status") |
| | | private String plcConnectionStatus; |
| | | |
| | | @ApiModelProperty(value = "å½åæä½") |
| | | @TableField("current_operation") |
| | | private String currentOperation; |
| | | |
| | | @ApiModelProperty(value = "æä½è¿åº¦(0-100)") |
| | | @TableField("operation_progress") |
| | | private BigDecimal operationProgress; |
| | | |
| | | @ApiModelProperty(value = "åè¦ä¿¡æ¯") |
| | | @TableField("alert_message") |
| | | private String alertMessage; |
| | | |
| | | @ApiModelProperty(value = "è®°å½æ¶é´") |
| | | @TableField("created_time") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | private Date createdTime; |
| | | |
| | | // 设å¤ç¶æå¸¸é |
| | | public static final class Status { |
| | | public static final String ONLINE = "ONLINE"; // å¨çº¿ |
| | | public static final String OFFLINE = "OFFLINE"; // 离线 |
| | | public static final String BUSY = "BUSY"; // å¿ç¢ |
| | | public static final String ERROR = "ERROR"; // é误 |
| | | public static final String MAINTENANCE = "MAINTENANCE"; // ç»´æ¤ä¸ |
| | | } |
| | | |
| | | // PLCè¿æ¥ç¶æå¸¸é |
| | | public static final class PlcConnectionStatus { |
| | | public static final String CONNECTED = "CONNECTED"; // å·²è¿æ¥ |
| | | public static final String DISCONNECTED = "DISCONNECTED"; // æªè¿æ¥ |
| | | public static final String ERROR = "ERROR"; // è¿æ¥é误 |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.mes.device.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.Date; |
| | | |
| | | /** |
| | | * ç»çä¿¡æ¯å®ä½ç±» |
| | | * å¯¹åºæ°æ®åºè¡¨ï¼glass_info |
| | | */ |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = false) |
| | | @TableName("glass_info") |
| | | @ApiModel(value = "GlassInfo", description = "ç»çä¿¡æ¯") |
| | | public class GlassInfo { |
| | | |
| | | @ApiModelProperty(value = "主é®ID", example = "1") |
| | | @TableId(value = "id", type = IdType.AUTO) |
| | | private Long id; |
| | | |
| | | @ApiModelProperty(value = "ç»çIDï¼å¯ä¸æ è¯ï¼", example = "GLS-2024-001") |
| | | @TableField("glass_id") |
| | | private String glassId; |
| | | |
| | | @ApiModelProperty(value = "ç»çé¿åº¦ï¼mmï¼", example = "2000") |
| | | @TableField("glass_length") |
| | | private Integer glassLength; |
| | | |
| | | @ApiModelProperty(value = "ç»ç宽度ï¼mmï¼", example = "1500") |
| | | @TableField("glass_width") |
| | | private Integer glassWidth; |
| | | |
| | | @ApiModelProperty(value = "ç»çå度ï¼mmï¼", example = "5.0") |
| | | @TableField("glass_thickness") |
| | | private BigDecimal glassThickness; |
| | | |
| | | @ApiModelProperty(value = "ç»çç±»å", example = "æ®éç»ç") |
| | | @TableField("glass_type") |
| | | private String glassType; |
| | | |
| | | @ApiModelProperty(value = "ç产åå", example = "ååA") |
| | | @TableField("manufacturer") |
| | | private String manufacturer; |
| | | |
| | | @ApiModelProperty(value = "çäº§æ¥æ") |
| | | @TableField("production_date") |
| | | @JsonFormat(pattern = "yyyy-MM-dd") |
| | | private Date productionDate; |
| | | |
| | | @ApiModelProperty(value = "ç¶æï¼ACTIVE-æ´»è·, ARCHIVED-已彿¡£", example = "ACTIVE") |
| | | @TableField("status") |
| | | private String status; |
| | | |
| | | @ApiModelProperty(value = "æè¿°ä¿¡æ¯") |
| | | @TableField("description") |
| | | private String description; |
| | | |
| | | @ApiModelProperty(value = "å建æ¶é´") |
| | | @TableField(value = "created_time", fill = FieldFill.INSERT) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | private Date createdTime; |
| | | |
| | | @ApiModelProperty(value = "æ´æ°æ¶é´") |
| | | @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE) |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
| | | private Date updatedTime; |
| | | |
| | | @ApiModelProperty(value = "å建人", example = "system") |
| | | @TableField(value = "created_by", fill = FieldFill.INSERT) |
| | | private String createdBy; |
| | | |
| | | @ApiModelProperty(value = "æ´æ°äºº", example = "system") |
| | | @TableField(value = "updated_by", fill = FieldFill.INSERT_UPDATE) |
| | | private String updatedBy; |
| | | |
| | | @ApiModelProperty(value = "æ¯å¦å é¤ï¼0-å¦ï¼1-æ¯", example = "0") |
| | | @TableField("is_deleted") |
| | | @TableLogic |
| | | private Integer isDeleted; |
| | | |
| | | // ç¶æå¸¸é |
| | | public static final class Status { |
| | | public static final String ACTIVE = "ACTIVE"; // æ´»è· |
| | | public static final String ARCHIVED = "ARCHIVED"; // 已彿¡£ |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.mes.device.entity.request; |
| | | |
| | | import com.github.xingshuangs.iot.common.enums.EDataType; |
| | | import com.github.xingshuangs.iot.protocol.s7.serializer.S7Variable; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * ä¸å¤§è½¦è®¾å¤è¯·æ±å®ä½ |
| | | * ç¨äºå®ä¹ä¸å¤§è½¦è®¾å¤åPLCåå
¥çåæ®µç»æ |
| | | * åæ®µå°åæ å°éè¿DeviceConfig.configJsonä¸çaddressMappingé
ç½® |
| | | * |
| | | * @author mes |
| | | * @since 2025-11-19 |
| | | */ |
| | | @Data |
| | | public class LoadVehicleRequest { |
| | | |
| | | /** |
| | | * 请æ±å 0æ è¯·æ± 1æè¯·æ±ï¼ä¸å¤§è½¦æ¸
0ï¼ |
| | | */ |
| | | @S7Variable(address = "plcRequest", type = EDataType.UINT16) |
| | | private Integer plcRequest; |
| | | |
| | | /** |
| | | * è¿çä½ç½® |
| | | */ |
| | | @S7Variable(address = "inPosition", type = EDataType.UINT16) |
| | | private Integer inPosition; |
| | | |
| | | /** |
| | | * ç»çID1 |
| | | */ |
| | | @S7Variable(address = "plcGlassId1", type = EDataType.STRING, count = 20) |
| | | private String plcGlassId1; |
| | | |
| | | /** |
| | | * ç»çID2 |
| | | */ |
| | | @S7Variable(address = "plcGlassId2", type = EDataType.STRING, count = 20) |
| | | private String plcGlassId2; |
| | | |
| | | /** |
| | | * ç»çID3 |
| | | */ |
| | | @S7Variable(address = "plcGlassId3", type = EDataType.STRING, count = 20) |
| | | private String plcGlassId3; |
| | | |
| | | /** |
| | | * ç»çID4 |
| | | */ |
| | | @S7Variable(address = "plcGlassId4", type = EDataType.STRING, count = 20) |
| | | private String plcGlassId4; |
| | | |
| | | /** |
| | | * ç»çID5 |
| | | */ |
| | | @S7Variable(address = "plcGlassId5", type = EDataType.STRING, count = 20) |
| | | private String plcGlassId5; |
| | | |
| | | /** |
| | | * ç»çID6 |
| | | */ |
| | | @S7Variable(address = "plcGlassId6", type = EDataType.STRING, count = 20) |
| | | private String plcGlassId6; |
| | | |
| | | /** |
| | | * ç»çæ°é |
| | | */ |
| | | @S7Variable(address = "plcGlassCount", type = EDataType.UINT16) |
| | | private Integer plcGlassCount; |
| | | } |
| | | |
| New file |
| | |
| | | package com.mes.device.entity.request; |
| | | |
| | | import cn.hutool.core.collection.CollectionUtil; |
| | | import com.github.xingshuangs.iot.common.enums.EDataType; |
| | | import com.github.xingshuangs.iot.protocol.s7.serializer.S7Variable; |
| | | import com.mes.vertical.history.VerticalSheetCageHistoryTask; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.List; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * @Author : zhoush |
| | | * @Date: 2025/6/15 15:15 |
| | | * @Description: |
| | | */ |
| | | @ApiModel(description = ":") |
| | | @Data |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Builder |
| | | public class VerticalCarData { |
| | | |
| | | @ApiModelProperty(value = "èæºç¶æ", position = 1) |
| | | @S7Variable(address = "verticalCar.onlineState", type = EDataType.BOOL) |
| | | private Boolean onlineState; |
| | | |
| | | @ApiModelProperty(value = "请æ±å", position = 2) |
| | | @S7Variable(address = "verticalCar.plcRequest", type = EDataType.UINT16) |
| | | private Integer plcRequest; |
| | | |
| | | @ApiModelProperty(value = "æ±æ¥å", position = 3) |
| | | @S7Variable(address = "verticalCar.reportWord", type = EDataType.UINT16) |
| | | private Integer reportWord; |
| | | |
| | | @ApiModelProperty(value = "1ç¶æ", position = 4) |
| | | @S7Variable(address = "verticalCar.state1", type = EDataType.UINT16) |
| | | private Integer state1; |
| | | |
| | | @ApiModelProperty(value = "2ç¶æ", position = 5) |
| | | @S7Variable(address = "verticalCar.state2", type = EDataType.UINT16) |
| | | private Integer state2; |
| | | |
| | | @ApiModelProperty(value = "3ç¶æ", position = 6) |
| | | @S7Variable(address = "verticalCar.state3", type = EDataType.UINT16) |
| | | private Integer state3; |
| | | |
| | | @ApiModelProperty(value = "4ç¶æ", position = 7) |
| | | @S7Variable(address = "verticalCar.state4", type = EDataType.UINT16) |
| | | private Integer state4; |
| | | |
| | | @ApiModelProperty(value = "5ç¶æ", position = 8) |
| | | @S7Variable(address = "verticalCar.state5", type = EDataType.UINT16) |
| | | private Integer state5; |
| | | |
| | | @ApiModelProperty(value = "6ç¶æ", position = 9) |
| | | @S7Variable(address = "verticalCar.state6", type = EDataType.UINT16) |
| | | private Integer state6; |
| | | |
| | | @ApiModelProperty(value = "åéå", position = 10) |
| | | @S7Variable(address = "verticalCar.mesSend", type = EDataType.UINT16) |
| | | private Integer mesSend; |
| | | |
| | | @ApiModelProperty(value = "确认å", position = 11) |
| | | @S7Variable(address = "verticalCar.confirmWord", type = EDataType.UINT16) |
| | | private Integer confirmWord; |
| | | |
| | | @ApiModelProperty(value = "车次信æ¯", position = 12) |
| | | @S7Variable(address = "verticalCar.trainInfo", type = EDataType.STRING, count = 20) |
| | | private String trainInfo; |
| | | |
| | | @ApiModelProperty(value = "ç»çid1", position = 13) |
| | | @S7Variable(address = "verticalCar.mesGlassId1", type = EDataType.STRING, count = 20) |
| | | private String mesGlassId1; |
| | | |
| | | @ApiModelProperty(value = "ç»çid2", position = 14) |
| | | @S7Variable(address = "verticalCar.mesGlassId2", type = EDataType.STRING, count = 20) |
| | | private String mesGlassId2; |
| | | |
| | | @ApiModelProperty(value = "ç»çid3", position = 15) |
| | | @S7Variable(address = "verticalCar.mesGlassId3", type = EDataType.STRING, count = 20) |
| | | private String mesGlassId3; |
| | | |
| | | @ApiModelProperty(value = "ç»çid4", position = 16) |
| | | @S7Variable(address = "verticalCar.mesGlassId4", type = EDataType.STRING, count = 20) |
| | | private String mesGlassId4; |
| | | |
| | | @ApiModelProperty(value = "ç»çid5", position = 17) |
| | | @S7Variable(address = "verticalCar.mesGlassId5", type = EDataType.STRING, count = 20) |
| | | private String mesGlassId5; |
| | | |
| | | @ApiModelProperty(value = "ç»çid6", position = 18) |
| | | @S7Variable(address = "verticalCar.mesGlassId6", type = EDataType.STRING, count = 20) |
| | | private String mesGlassId6; |
| | | |
| | | @ApiModelProperty(value = "èµ·å§æ ¼å1", position = 19) |
| | | @S7Variable(address = "verticalCar.start1", type = EDataType.UINT16) |
| | | private Integer start1; |
| | | |
| | | @ApiModelProperty(value = "èµ·å§æ ¼å2", position = 20) |
| | | @S7Variable(address = "verticalCar.start2", type = EDataType.UINT16) |
| | | private Integer start2; |
| | | |
| | | @ApiModelProperty(value = "èµ·å§æ ¼å3", position = 21) |
| | | @S7Variable(address = "verticalCar.start3", type = EDataType.UINT16) |
| | | private Integer start3; |
| | | |
| | | @ApiModelProperty(value = "èµ·å§æ ¼å4", position = 22) |
| | | @S7Variable(address = "verticalCar.start4", type = EDataType.UINT16) |
| | | private Integer start4; |
| | | |
| | | @ApiModelProperty(value = "èµ·å§æ ¼å5", position = 23) |
| | | @S7Variable(address = "verticalCar.start5", type = EDataType.UINT16) |
| | | private Integer start5; |
| | | |
| | | @ApiModelProperty(value = "èµ·å§æ ¼å6", position = 24) |
| | | @S7Variable(address = "verticalCar.start6", type = EDataType.UINT16) |
| | | private Integer start6; |
| | | |
| | | @ApiModelProperty(value = "ç®æ æ ¼å1", position = 25) |
| | | @S7Variable(address = "verticalCar.target1", type = EDataType.UINT16) |
| | | private Integer target1; |
| | | |
| | | @ApiModelProperty(value = "ç®æ æ ¼å2", position = 26) |
| | | @S7Variable(address = "verticalCar.target2", type = EDataType.UINT16) |
| | | private Integer target2; |
| | | |
| | | @ApiModelProperty(value = "ç®æ æ ¼å3", position = 27) |
| | | @S7Variable(address = "verticalCar.target3", type = EDataType.UINT16) |
| | | private Integer target3; |
| | | |
| | | @ApiModelProperty(value = "ç®æ æ ¼å4", position = 28) |
| | | @S7Variable(address = "verticalCar.target4", type = EDataType.UINT16) |
| | | private Integer target4; |
| | | |
| | | @ApiModelProperty(value = "ç®æ æ ¼å5", position = 29) |
| | | @S7Variable(address = "verticalCar.target5", type = EDataType.UINT16) |
| | | private Integer target5; |
| | | |
| | | @ApiModelProperty(value = "ç®æ æ ¼å6", position = 30) |
| | | @S7Variable(address = "verticalCar.target6", type = EDataType.UINT16) |
| | | private Integer target6; |
| | | |
| | | @ApiModelProperty(value = "é¿è¾¹1", position = 31) |
| | | @S7Variable(address = "verticalCar.width1", type = EDataType.UINT16) |
| | | private Integer width1; |
| | | |
| | | @ApiModelProperty(value = "é¿è¾¹2", position = 32) |
| | | @S7Variable(address = "verticalCar.width2", type = EDataType.UINT16) |
| | | private Integer width2; |
| | | |
| | | @ApiModelProperty(value = "é¿è¾¹3", position = 33) |
| | | @S7Variable(address = "verticalCar.width3", type = EDataType.UINT16) |
| | | private Integer width3; |
| | | |
| | | @ApiModelProperty(value = "é¿è¾¹4", position = 34) |
| | | @S7Variable(address = "verticalCar.width4", type = EDataType.UINT16) |
| | | private Integer width4; |
| | | |
| | | @ApiModelProperty(value = "é¿è¾¹5", position = 35) |
| | | @S7Variable(address = "verticalCar.width5", type = EDataType.UINT16) |
| | | private Integer width5; |
| | | |
| | | @ApiModelProperty(value = "é¿è¾¹6", position = 36) |
| | | @S7Variable(address = "verticalCar.width6", type = EDataType.UINT16) |
| | | private Integer width6; |
| | | |
| | | @ApiModelProperty(value = "çè¾¹1", position = 37) |
| | | @S7Variable(address = "verticalCar.height1", type = EDataType.UINT16) |
| | | private Integer height1; |
| | | |
| | | @ApiModelProperty(value = "çè¾¹2", position = 38) |
| | | @S7Variable(address = "verticalCar.height2", type = EDataType.UINT16) |
| | | private Integer height2; |
| | | |
| | | @ApiModelProperty(value = "çè¾¹3", position = 39) |
| | | @S7Variable(address = "verticalCar.height3", type = EDataType.UINT16) |
| | | private Integer height3; |
| | | |
| | | @ApiModelProperty(value = "çè¾¹4", position = 40) |
| | | @S7Variable(address = "verticalCar.height4", type = EDataType.UINT16) |
| | | private Integer height4; |
| | | |
| | | @ApiModelProperty(value = "çè¾¹5", position = 41) |
| | | @S7Variable(address = "verticalCar.height5", type = EDataType.UINT16) |
| | | private Integer height5; |
| | | |
| | | @ApiModelProperty(value = "çè¾¹6", position = 42) |
| | | @S7Variable(address = "verticalCar.height6", type = EDataType.UINT16) |
| | | private Integer height6; |
| | | |
| | | @ApiModelProperty(value = "å1", position = 43) |
| | | @S7Variable(address = "verticalCar.thickness1", type = EDataType.UINT16) |
| | | private Integer thickness1; |
| | | |
| | | @ApiModelProperty(value = "å2", position = 44) |
| | | @S7Variable(address = "verticalCar.thickness2", type = EDataType.UINT16) |
| | | private Integer thickness2; |
| | | |
| | | @ApiModelProperty(value = "å3", position = 45) |
| | | @S7Variable(address = "verticalCar.thickness3", type = EDataType.UINT16) |
| | | private Integer thickness3; |
| | | |
| | | @ApiModelProperty(value = "å4", position = 46) |
| | | @S7Variable(address = "verticalCar.thickness4", type = EDataType.UINT16) |
| | | private Integer thickness4; |
| | | |
| | | @ApiModelProperty(value = "å5", position = 47) |
| | | @S7Variable(address = "verticalCar.thickness5", type = EDataType.UINT16) |
| | | private Integer thickness5; |
| | | |
| | | @ApiModelProperty(value = "å6", position = 48) |
| | | @S7Variable(address = "verticalCar.thickness6", type = EDataType.UINT16) |
| | | private Integer thickness6; |
| | | |
| | | @ApiModelProperty(value = "é è¾¹è·1", position = 49) |
| | | @S7Variable(address = "verticalCar.edgeDistance1", type = EDataType.UINT16) |
| | | private Integer edgeDistance1; |
| | | |
| | | @ApiModelProperty(value = "é è¾¹è·2", position = 50) |
| | | @S7Variable(address = "verticalCar.edgeDistance2", type = EDataType.UINT16) |
| | | private Integer edgeDistance2; |
| | | |
| | | @ApiModelProperty(value = "é è¾¹è·3", position = 51) |
| | | @S7Variable(address = "verticalCar.edgeDistance3", type = EDataType.UINT16) |
| | | private Integer edgeDistance3; |
| | | |
| | | @ApiModelProperty(value = "é è¾¹è·4", position = 52) |
| | | @S7Variable(address = "verticalCar.edgeDistance4", type = EDataType.UINT16) |
| | | private Integer edgeDistance4; |
| | | |
| | | @ApiModelProperty(value = "é è¾¹è·5", position = 53) |
| | | @S7Variable(address = "verticalCar.edgeDistance5", type = EDataType.UINT16) |
| | | private Integer edgeDistance5; |
| | | |
| | | @ApiModelProperty(value = "é è¾¹è·6", position = 54) |
| | | @S7Variable(address = "verticalCar.edgeDistance6", type = EDataType.UINT16) |
| | | private Integer edgeDistance6; |
| | | |
| | | @ApiModelProperty(value = "ç®æ é è¾¹è·1", position = 55) |
| | | @S7Variable(address = "verticalCar.targetEdgeDistance1", type = EDataType.UINT16) |
| | | private Integer targetEdgeDistance1; |
| | | |
| | | @ApiModelProperty(value = "ç®æ é è¾¹è·2", position = 56) |
| | | @S7Variable(address = "verticalCar.targetEdgeDistance2", type = EDataType.UINT16) |
| | | private Integer targetEdgeDistance2; |
| | | |
| | | @ApiModelProperty(value = "ç®æ é è¾¹è·3", position = 57) |
| | | @S7Variable(address = "verticalCar.targetEdgeDistance3", type = EDataType.UINT16) |
| | | private Integer targetEdgeDistance3; |
| | | |
| | | @ApiModelProperty(value = "ç®æ é è¾¹è·4", position = 58) |
| | | @S7Variable(address = "verticalCar.targetEdgeDistance4", type = EDataType.UINT16) |
| | | private Integer targetEdgeDistance4; |
| | | |
| | | @ApiModelProperty(value = "ç®æ é è¾¹è·5", position = 59) |
| | | @S7Variable(address = "verticalCar.targetEdgeDistance5", type = EDataType.UINT16) |
| | | private Integer targetEdgeDistance5; |
| | | |
| | | @ApiModelProperty(value = "ç®æ é è¾¹è·6", position = 60) |
| | | @S7Variable(address = "verticalCar.targetEdgeDistance6", type = EDataType.UINT16) |
| | | private Integer targetEdgeDistance6; |
| | | |
| | | @ApiModelProperty(value = "æ¥è¦ä¿¡å·", position = 61) |
| | | @S7Variable(address = "verticalCar.alarmSignal", type = EDataType.UINT16) |
| | | private Integer alarmSignal; |
| | | |
| | | public List<Integer> getStartSlots() { |
| | | return Arrays.asList(start1, start2, start3, start4, start5, start6); |
| | | } |
| | | |
| | | public List<Integer> getTargetSlots() { |
| | | return Arrays.asList(target1, target2, target3, target4, target5, target6); |
| | | } |
| | | |
| | | public List<Integer> getStates() { |
| | | return Arrays.asList(state1, state2, state3, state4, state5, state6); |
| | | } |
| | | |
| | | public List<String> getGlassIds() { |
| | | return Arrays.asList(mesGlassId1, mesGlassId2, mesGlassId3, mesGlassId4, mesGlassId5, mesGlassId6) |
| | | .stream() |
| | | .filter(glassId -> glassId != null && !glassId.trim().isEmpty()) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | public List<VerticalSheetCageHistoryTask> getTaskList() { |
| | | List<VerticalSheetCageHistoryTask> inTaskList = new ArrayList(); |
| | | List<String> glassIdList = this.getGlassIds(); |
| | | if (CollectionUtil.isEmpty(glassIdList)) { |
| | | return inTaskList; |
| | | } |
| | | List<Integer> targetList = this.getTargetSlots(); |
| | | List<Integer> stateList = this.getStates(); |
| | | List<Integer> startList = this.getStartSlots(); |
| | | for (int i = 0; i < glassIdList.size(); i++) { |
| | | VerticalSheetCageHistoryTask task = new VerticalSheetCageHistoryTask(); |
| | | task.setGlassId(glassIdList.get(i)); |
| | | task.setTargetSlot(targetList.get(i)); |
| | | task.setTaskState(stateList.get(i)); |
| | | task.setStartSlot(startList.get(i)); |
| | | inTaskList.add(task); |
| | | } |
| | | return inTaskList; |
| | | } |
| | | |
| | | public VerticalCarData setGlassIdsAndPosition(List<String> glassIds, Integer inPosition) { |
| | | VerticalCarData verticalCarData = new VerticalCarData(); |
| | | verticalCarData.setTrainInfo(inPosition.toString()); |
| | | int i = 1; |
| | | for (String glassId : glassIds |
| | | ) { |
| | | switch (i) { |
| | | case 1: |
| | | verticalCarData.setMesGlassId1(glassId); |
| | | verticalCarData.setStart1(inPosition); |
| | | break; |
| | | case 2: |
| | | verticalCarData.setMesGlassId2(glassId); |
| | | verticalCarData.setStart2(inPosition); |
| | | break; |
| | | case 3: |
| | | verticalCarData.setMesGlassId3(glassId); |
| | | verticalCarData.setStart3(inPosition); |
| | | break; |
| | | case 4: |
| | | verticalCarData.setMesGlassId4(glassId); |
| | | verticalCarData.setStart4(inPosition); |
| | | break; |
| | | case 5: |
| | | verticalCarData.setMesGlassId5(glassId); |
| | | verticalCarData.setStart5(inPosition); |
| | | break; |
| | | case 6: |
| | | verticalCarData.setMesGlassId6(glassId); |
| | | verticalCarData.setStart6(inPosition); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | i++; |
| | | } |
| | | return verticalCarData; |
| | | } |
| | | |
| | | public VerticalCarData setMesGlassInfo(VerticalCarData verticalCarData, List<VerticalSheetCageHistoryTask> verticalSheetCageHistoryTasks) { |
| | | int i = 1; |
| | | for (VerticalSheetCageHistoryTask verticalSheetCageHistoryTask : verticalSheetCageHistoryTasks |
| | | ) { |
| | | switch (i) { |
| | | case 1: |
| | | verticalCarData.setMesGlassId1(verticalSheetCageHistoryTask.getGlassId()); |
| | | verticalCarData.setStart1(verticalSheetCageHistoryTask.getStartSlot()); |
| | | verticalCarData.setTarget1(verticalSheetCageHistoryTask.getTargetSlot()); |
| | | verticalCarData.setWidth1(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setHeight1(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setThickness1((int) verticalSheetCageHistoryTask.getThickness().doubleValue()); |
| | | verticalCarData.setEdgeDistance1(verticalSheetCageHistoryTask.getEdgeDistance()); |
| | | verticalCarData.setTargetEdgeDistance1(verticalSheetCageHistoryTask.getTargetEdgeDistance()); |
| | | break; |
| | | case 2: |
| | | verticalCarData.setMesGlassId2(verticalSheetCageHistoryTask.getGlassId()); |
| | | verticalCarData.setStart2(verticalSheetCageHistoryTask.getStartSlot()); |
| | | verticalCarData.setTarget2(verticalSheetCageHistoryTask.getTargetSlot()); |
| | | verticalCarData.setWidth2(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setHeight2(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setThickness2((int) verticalSheetCageHistoryTask.getThickness().doubleValue()); |
| | | verticalCarData.setEdgeDistance2(verticalSheetCageHistoryTask.getEdgeDistance()); |
| | | verticalCarData.setTargetEdgeDistance2(verticalSheetCageHistoryTask.getTargetEdgeDistance()); |
| | | break; |
| | | case 3: |
| | | // 第ä¸ç»ç»çæ°æ®èµå¼ |
| | | verticalCarData.setMesGlassId3(verticalSheetCageHistoryTask.getGlassId()); |
| | | verticalCarData.setStart3(verticalSheetCageHistoryTask.getStartSlot()); |
| | | verticalCarData.setTarget3(verticalSheetCageHistoryTask.getTargetSlot()); |
| | | verticalCarData.setWidth3(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setHeight3(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setThickness3(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setEdgeDistance3(verticalSheetCageHistoryTask.getEdgeDistance()); |
| | | verticalCarData.setTargetEdgeDistance3(verticalSheetCageHistoryTask.getTargetEdgeDistance()); |
| | | break; |
| | | case 4: |
| | | verticalCarData.setMesGlassId4(verticalSheetCageHistoryTask.getGlassId()); |
| | | verticalCarData.setStart4(verticalSheetCageHistoryTask.getStartSlot()); |
| | | verticalCarData.setTarget4(verticalSheetCageHistoryTask.getTargetSlot()); |
| | | verticalCarData.setWidth4(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setHeight4(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setThickness4((int) verticalSheetCageHistoryTask.getThickness().doubleValue()); |
| | | verticalCarData.setEdgeDistance4(verticalSheetCageHistoryTask.getEdgeDistance()); |
| | | verticalCarData.setTargetEdgeDistance4(verticalSheetCageHistoryTask.getTargetEdgeDistance()); |
| | | break; |
| | | case 5: |
| | | verticalCarData.setMesGlassId5(verticalSheetCageHistoryTask.getGlassId()); |
| | | verticalCarData.setStart5(verticalSheetCageHistoryTask.getStartSlot()); |
| | | verticalCarData.setTarget5(verticalSheetCageHistoryTask.getTargetSlot()); |
| | | verticalCarData.setWidth5(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setHeight5(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setThickness5((int) verticalSheetCageHistoryTask.getThickness().doubleValue()); |
| | | verticalCarData.setEdgeDistance5(verticalSheetCageHistoryTask.getEdgeDistance()); |
| | | verticalCarData.setTargetEdgeDistance5(verticalSheetCageHistoryTask.getTargetEdgeDistance()); |
| | | break; |
| | | case 6: |
| | | verticalCarData.setMesGlassId6(verticalSheetCageHistoryTask.getGlassId()); |
| | | verticalCarData.setStart6(verticalSheetCageHistoryTask.getStartSlot()); |
| | | verticalCarData.setTarget6(verticalSheetCageHistoryTask.getTargetSlot()); |
| | | verticalCarData.setWidth6(Math.max((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setHeight6(Math.min((int) verticalSheetCageHistoryTask.getWidth().doubleValue(), (int) verticalSheetCageHistoryTask.getHeight().doubleValue())); |
| | | verticalCarData.setThickness6((int) verticalSheetCageHistoryTask.getThickness().doubleValue()); |
| | | verticalCarData.setEdgeDistance6(verticalSheetCageHistoryTask.getEdgeDistance()); |
| | | verticalCarData.setTargetEdgeDistance6(verticalSheetCageHistoryTask.getTargetEdgeDistance()); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | i++; |
| | | } |
| | | return verticalCarData; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.mes.device.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.mes.device.entity.GlassInfo; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | import org.apache.ibatis.annotations.Select; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 设å¤ç»çä¿¡æ¯Mapperæ¥å£ |
| | | * |
| | | * @author mes |
| | | * @since 2024-11-20 |
| | | */ |
| | | @Mapper |
| | | public interface DeviceGlassInfoMapper extends BaseMapper<GlassInfo> { |
| | | |
| | | /** |
| | | * æ ¹æ®ç»çIDæ¥è¯¢ç»çä¿¡æ¯ |
| | | * |
| | | * @param glassId ç»çID |
| | | * @return ç»çä¿¡æ¯ |
| | | */ |
| | | @Select("SELECT * FROM glass_info WHERE glass_id = #{glassId} AND is_deleted = 0 LIMIT 1") |
| | | GlassInfo selectByGlassId(@Param("glassId") String glassId); |
| | | |
| | | /** |
| | | * æ ¹æ®ç»çIDå表æ¹éæ¥è¯¢ç»çä¿¡æ¯ |
| | | * |
| | | * @param glassIds ç»çIDå表 |
| | | * @return ç»çä¿¡æ¯å表 |
| | | */ |
| | | List<GlassInfo> selectByGlassIds(@Param("glassIds") List<String> glassIds); |
| | | |
| | | /** |
| | | * æ ¹æ®ç¶ææ¥è¯¢ç»çä¿¡æ¯å表 |
| | | * |
| | | * @param status ç¶æ |
| | | * @return ç»çä¿¡æ¯å表 |
| | | */ |
| | | @Select("SELECT * FROM glass_info WHERE status = #{status} AND is_deleted = 0 ORDER BY created_time DESC") |
| | | List<GlassInfo> selectByStatus(@Param("status") String status); |
| | | } |
| | | |
| | |
| | | * @return 设å¤ä¿¡æ¯å表 |
| | | */ |
| | | @Select("SELECT d.id, d.device_name as deviceName, d.device_code as deviceCode, " + |
| | | "d.device_type as deviceType, dgr.role, d.status, " + |
| | | "d.last_heartbeat as lastHeartbeat, d.is_online as isOnline " + |
| | | "d.device_type as deviceType, d.plc_ip as plcIp, dgr.role, d.status, " + |
| | | "ds.last_heartbeat as lastHeartbeat, " + |
| | | "CASE WHEN ds.status = 'ONLINE' THEN TRUE ELSE FALSE END as isOnline " + |
| | | "FROM device_config d " + |
| | | "INNER JOIN device_group_relation dgr ON d.id = dgr.device_id " + |
| | | "WHERE dgr.group_id = #{groupId} AND dgr.is_deleted = 0 AND d.is_deleted = 0") |
| | | "LEFT JOIN device_status ds ON d.device_id = ds.device_id " + |
| | | " AND ds.id = (SELECT MAX(id) FROM device_status WHERE device_id = d.device_id) " + |
| | | "WHERE dgr.group_id = #{groupId} AND dgr.is_deleted = 0 AND d.is_deleted = 0 " + |
| | | "ORDER BY dgr.connection_order ASC") |
| | | List<DeviceGroupVO.DeviceInfo> getGroupDevices(@Param("groupId") Long groupId); |
| | | |
| | | /** |
| | |
| | | " AND d.is_deleted = 0 " + |
| | | "ORDER BY IFNULL(dgr.connection_order, 0) ASC, dgr.id ASC") |
| | | List<DeviceConfig> getOrderedDeviceConfigs(@Param("groupId") Long groupId); |
| | | |
| | | /** |
| | | * è·å设å¤ç»ä¸çå¨çº¿è®¾å¤æ°é |
| | | * |
| | | * @param groupId 设å¤ç»ID |
| | | * @return å¨çº¿è®¾å¤æ°é |
| | | */ |
| | | @Select("SELECT COUNT(DISTINCT d.id) " + |
| | | "FROM device_config d " + |
| | | "INNER JOIN device_group_relation dgr ON d.id = dgr.device_id " + |
| | | "LEFT JOIN device_status ds ON d.device_id = ds.device_id " + |
| | | " AND ds.id = (SELECT MAX(id) FROM device_status WHERE device_id = d.device_id) " + |
| | | "WHERE dgr.group_id = #{groupId} " + |
| | | " AND dgr.is_deleted = 0 " + |
| | | " AND d.is_deleted = 0 " + |
| | | " AND ds.status = 'ONLINE'") |
| | | Integer getOnlineDeviceCountByGroupId(@Param("groupId") Long groupId); |
| | | } |
| New file |
| | |
| | | package com.mes.device.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.mes.device.entity.DeviceStatus; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import org.apache.ibatis.annotations.Param; |
| | | import org.apache.ibatis.annotations.Select; |
| | | import org.apache.ibatis.annotations.Update; |
| | | |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 设å¤ç¶æMapper |
| | | */ |
| | | @Mapper |
| | | public interface DeviceStatusMapper extends BaseMapper<DeviceStatus> { |
| | | |
| | | /** |
| | | * æ ¹æ®è®¾å¤IDè·åææ°ç设å¤ç¶æ |
| | | */ |
| | | @Select("SELECT * FROM device_status WHERE device_id = #{deviceId} " + |
| | | "ORDER BY id DESC LIMIT 1") |
| | | DeviceStatus getLatestByDeviceId(@Param("deviceId") String deviceId); |
| | | |
| | | /** |
| | | * æ´æ°è®¾å¤ç¶æï¼æ´æ°ææ°è®°å½ï¼ |
| | | */ |
| | | @Update("UPDATE device_status ds1 " + |
| | | "INNER JOIN (" + |
| | | " SELECT MAX(id) as max_id FROM device_status WHERE device_id = #{deviceId}" + |
| | | ") ds2 ON ds1.id = ds2.max_id " + |
| | | "SET ds1.status = #{status}, ds1.last_heartbeat = #{lastHeartbeat} " + |
| | | "WHERE ds1.device_id = #{deviceId}") |
| | | int updateLatestStatus(@Param("deviceId") String deviceId, |
| | | @Param("status") String status, |
| | | @Param("lastHeartbeat") Date lastHeartbeat); |
| | | |
| | | /** |
| | | * æ ¹æ®è®¾å¤IDå表è·åææ°ç设å¤ç¶æ |
| | | */ |
| | | @Select("<script>" + |
| | | "SELECT ds1.* FROM device_status ds1 " + |
| | | "INNER JOIN (" + |
| | | " SELECT device_id, MAX(id) as max_id " + |
| | | " FROM device_status " + |
| | | " WHERE device_id IN " + |
| | | " <foreach collection='deviceIds' item='deviceId' open='(' separator=',' close=')'>" + |
| | | " #{deviceId}" + |
| | | " </foreach>" + |
| | | " GROUP BY device_id" + |
| | | ") ds2 ON ds1.device_id = ds2.device_id AND ds1.id = ds2.max_id" + |
| | | "</script>") |
| | | List<DeviceStatus> getLatestByDeviceIds(@Param("deviceIds") List<String> deviceIds); |
| | | } |
| | | |
| New file |
| | |
| | | package com.mes.device.service; |
| | | |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.device.entity.DeviceGroupConfig; |
| | | import com.mes.task.model.TaskExecutionContext; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 设å¤åè°æå¡ |
| | | * è´è´£è®¾å¤é´æ°æ®ä¼ éãç¶æåæ¥ãä¾èµç®¡ççåè°å·¥ä½ |
| | | * |
| | | * @author mes |
| | | * @since 2025-01-XX |
| | | */ |
| | | public interface DeviceCoordinationService { |
| | | |
| | | /** |
| | | * åè°è®¾å¤ç»æ§è¡ |
| | | * æ ¹æ®è®¾å¤ä¾èµå
³ç³»åæ§è¡é¡ºåºï¼åè°å¤ä¸ªè®¾å¤çæ§è¡ |
| | | * |
| | | * @param groupConfig 设å¤ç»é
ç½® |
| | | * @param devices 设å¤å表ï¼å·²ææ§è¡é¡ºåºæåºï¼ |
| | | * @param context 任塿§è¡ä¸ä¸æ |
| | | * @return åè°ç»æï¼å
嫿¯å¦å¯ä»¥æ§è¡ãä¾èµå
³ç³»çä¿¡æ¯ |
| | | */ |
| | | CoordinationResult coordinateExecution(DeviceGroupConfig groupConfig, |
| | | List<DeviceConfig> devices, |
| | | TaskExecutionContext context); |
| | | |
| | | /** |
| | | * ä¼ éæ°æ®å°ä¸ä¸ä¸ªè®¾å¤ |
| | | * å°å½å设å¤çæ°æ®ä¼ éç»ä¸ä¸ä¸ªè®¾å¤ |
| | | * |
| | | * @param fromDevice æºè®¾å¤ |
| | | * @param toDevice ç®æ è®¾å¤ |
| | | * @param data è¦ä¼ éçæ°æ® |
| | | * @param context 任塿§è¡ä¸ä¸æ |
| | | * @return æ¯å¦ä¼ éæå |
| | | */ |
| | | boolean transferData(DeviceConfig fromDevice, |
| | | DeviceConfig toDevice, |
| | | Map<String, Object> data, |
| | | TaskExecutionContext context); |
| | | |
| | | /** |
| | | * åæ¥è®¾å¤ç¶æ |
| | | * å°è®¾å¤ç¶æåæ¥å°å
±äº«ä¸ä¸æ |
| | | * |
| | | * @param device 设å¤é
ç½® |
| | | * @param status 设å¤ç¶æ |
| | | * @param context 任塿§è¡ä¸ä¸æ |
| | | */ |
| | | void syncDeviceStatus(DeviceConfig device, |
| | | DeviceStatus status, |
| | | TaskExecutionContext context); |
| | | |
| | | /** |
| | | * æ£æ¥è®¾å¤ä¾èµå
³ç³» |
| | | * æ£æ¥è®¾å¤æ¯å¦æ»¡è¶³æ§è¡çåç½®æ¡ä»¶ |
| | | * |
| | | * @param device 设å¤é
ç½® |
| | | * @param context 任塿§è¡ä¸ä¸æ |
| | | * @return ä¾èµæ£æ¥ç»æ |
| | | */ |
| | | DependencyCheckResult checkDependencies(DeviceConfig device, |
| | | TaskExecutionContext context); |
| | | |
| | | /** |
| | | * è·å设å¤ä¾èµç设å¤å表 |
| | | * |
| | | * @param device 设å¤é
ç½® |
| | | * @param groupConfig 设å¤ç»é
ç½® |
| | | * @return ä¾èµç设å¤å表 |
| | | */ |
| | | List<DeviceConfig> getDependentDevices(DeviceConfig device, |
| | | DeviceGroupConfig groupConfig); |
| | | |
| | | /** |
| | | * åè°ç»æ |
| | | */ |
| | | class CoordinationResult { |
| | | private final boolean canExecute; |
| | | private final String message; |
| | | private final Map<String, Object> metadata; |
| | | |
| | | public CoordinationResult(boolean canExecute, String message, Map<String, Object> metadata) { |
| | | this.canExecute = canExecute; |
| | | this.message = message; |
| | | this.metadata = metadata; |
| | | } |
| | | |
| | | public static CoordinationResult success(String message) { |
| | | return new CoordinationResult(true, message, null); |
| | | } |
| | | |
| | | public static CoordinationResult success(String message, Map<String, Object> metadata) { |
| | | return new CoordinationResult(true, message, metadata); |
| | | } |
| | | |
| | | public static CoordinationResult failure(String message) { |
| | | return new CoordinationResult(false, message, null); |
| | | } |
| | | |
| | | public boolean canExecute() { |
| | | return canExecute; |
| | | } |
| | | |
| | | public String getMessage() { |
| | | return message; |
| | | } |
| | | |
| | | public Map<String, Object> getMetadata() { |
| | | return metadata; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 设å¤ç¶æ |
| | | */ |
| | | enum DeviceStatus { |
| | | IDLE, // ç©ºé² |
| | | READY, // 就绪 |
| | | RUNNING, // è¿è¡ä¸ |
| | | COMPLETED, // 已宿 |
| | | FAILED, // 失败 |
| | | WAITING // çå¾
ä¸ |
| | | } |
| | | |
| | | /** |
| | | * ä¾èµæ£æ¥ç»æ |
| | | */ |
| | | class DependencyCheckResult { |
| | | private final boolean satisfied; |
| | | private final String message; |
| | | private final List<String> missingDependencies; |
| | | |
| | | public DependencyCheckResult(boolean satisfied, String message, List<String> missingDependencies) { |
| | | this.satisfied = satisfied; |
| | | this.message = message; |
| | | this.missingDependencies = missingDependencies; |
| | | } |
| | | |
| | | public static DependencyCheckResult satisfied() { |
| | | return new DependencyCheckResult(true, "ä¾èµæ¡ä»¶æ»¡è¶³", null); |
| | | } |
| | | |
| | | public static DependencyCheckResult unsatisfied(String message, List<String> missingDependencies) { |
| | | return new DependencyCheckResult(false, message, missingDependencies); |
| | | } |
| | | |
| | | public boolean isSatisfied() { |
| | | return satisfied; |
| | | } |
| | | |
| | | public String getMessage() { |
| | | return message; |
| | | } |
| | | |
| | | public List<String> getMissingDependencies() { |
| | | return missingDependencies; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | import com.mes.device.vo.DeviceGroupConfigVO; |
| | | import com.mes.device.vo.DeviceGroupVO; |
| | | import com.mes.device.vo.StatisticsVO; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | /** |
| | | * 设å¤ç»é
ç½®æå¡æ¥å£ |
| | | */ |
| | | @Service |
| | | public interface DeviceGroupConfigService extends IService<DeviceGroupConfig> { |
| | | |
| | | /** |
| New file |
| | |
| | | package com.mes.device.service; |
| | | |
| | | import com.mes.device.entity.DeviceStatus; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * 设å¤ç¶ææå¡æ¥å£ |
| | | */ |
| | | public interface DeviceStatusService { |
| | | |
| | | /** |
| | | * æ ¹æ®è®¾å¤IDè·åææ°ç设å¤ç¶æ |
| | | */ |
| | | DeviceStatus getLatestByDeviceId(String deviceId); |
| | | |
| | | /** |
| | | * æ ¹æ®è®¾å¤IDå表è·åææ°ç设å¤ç¶æ |
| | | */ |
| | | List<DeviceStatus> getLatestByDeviceIds(List<String> deviceIds); |
| | | |
| | | /** |
| | | * æ´æ°è®¾å¤å¨çº¿ç¶æï¼æå¨è®¾ç½®ï¼ |
| | | */ |
| | | boolean updateDeviceOnlineStatus(Long deviceId, String status); |
| | | |
| | | /** |
| | | * æ¹éæ´æ°è®¾å¤å¨çº¿ç¶æ |
| | | */ |
| | | boolean batchUpdateDeviceOnlineStatus(List<Long> deviceIds, String status); |
| | | |
| | | /** |
| | | * è®°å½è®¾å¤å¿è·³ï¼èªå¨æ´æ°å¨çº¿ç¶æï¼ |
| | | */ |
| | | boolean recordHeartbeat(String deviceId, String status); |
| | | |
| | | /** |
| | | * æ ¹æ®è®¾å¤é
ç½®IDè·å设å¤ç¶æ |
| | | */ |
| | | DeviceStatus getLatestByDeviceConfigId(Long deviceConfigId); |
| | | } |
| | | |
| New file |
| | |
| | | package com.mes.device.service; |
| | | |
| | | import com.mes.device.entity.GlassInfo; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * ç»çä¿¡æ¯æå¡æ¥å£ |
| | | * |
| | | * @author mes |
| | | * @since 2024-11-20 |
| | | */ |
| | | public interface GlassInfoService { |
| | | |
| | | /** |
| | | * æ ¹æ®ç»çIDæ¥è¯¢ç»çä¿¡æ¯ |
| | | * |
| | | * @param glassId ç»çID |
| | | * @return ç»çä¿¡æ¯ï¼å¦æä¸åå¨è¿ånull |
| | | */ |
| | | GlassInfo getGlassInfo(String glassId); |
| | | |
| | | /** |
| | | * æ ¹æ®ç»çIDè·åç»çé¿åº¦ |
| | | * |
| | | * @param glassId ç»çID |
| | | * @return ç»çé¿åº¦ï¼mmï¼ï¼å¦æä¸åå¨è¿ånull |
| | | */ |
| | | Integer getGlassLength(String glassId); |
| | | |
| | | /** |
| | | * æ ¹æ®ç»çIDå表æ¹éæ¥è¯¢ç»çä¿¡æ¯ |
| | | * |
| | | * @param glassIds ç»çIDå表 |
| | | * @return ç»çä¿¡æ¯å表 |
| | | */ |
| | | List<GlassInfo> getGlassInfos(List<String> glassIds); |
| | | |
| | | /** |
| | | * æ ¹æ®ç»çIDå表æ¹éè·åç»çé¿åº¦æ å° |
| | | * |
| | | * @param glassIds ç»çIDå表 |
| | | * @return ç»çIDå°é¿åº¦çæ å°Map |
| | | */ |
| | | Map<String, Integer> getGlassLengthMap(List<String> glassIds); |
| | | |
| | | /** |
| | | * åå»ºææ´æ°ç»çä¿¡æ¯ |
| | | * |
| | | * @param glassInfo ç»çä¿¡æ¯ |
| | | * @return æ¯å¦æå |
| | | */ |
| | | boolean saveOrUpdateGlassInfo(GlassInfo glassInfo); |
| | | |
| | | /** |
| | | * æ¹éåå»ºææ´æ°ç»çä¿¡æ¯ |
| | | * |
| | | * @param glassInfos ç»çä¿¡æ¯å表 |
| | | * @return æ¯å¦æå |
| | | */ |
| | | boolean batchSaveOrUpdateGlassInfo(List<GlassInfo> glassInfos); |
| | | } |
| | | |
| | |
| | | public class DeviceConfigServiceImpl extends ServiceImpl<DeviceConfigMapper, DeviceConfig> implements DeviceConfigService { |
| | | |
| | | private final ObjectMapper objectMapper = new ObjectMapper(); |
| | | private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {}; |
| | | |
| | | @Override |
| | | public boolean createDevice(DeviceConfig deviceConfig) { |
| | |
| | | vo.setStatus(getStatusName(device.getStatus())); |
| | | vo.setDeviceStatus(convertStatusToCode(device.getStatus())); |
| | | vo.setDescription(device.getDescription()); |
| | | vo.setLocation("é»è®¤ä½ç½®"); // TODO: 仿©å±åæ°æå
³è表ä¸è·å |
| | | vo.setLocation(extractLocationFromDevice(device)); |
| | | vo.setCreatedTime(device.getCreatedTime()); |
| | | vo.setUpdatedTime(device.getUpdatedTime()); |
| | | vo.setProjectId(device.getProjectId()); |
| | |
| | | return new ArrayList<>(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä»è®¾å¤æ©å±åæ°ä¸æåä½ç½®ä¿¡æ¯ |
| | | */ |
| | | private String extractLocationFromDevice(DeviceConfig device) { |
| | | if (device == null) { |
| | | return "é»è®¤ä½ç½®"; |
| | | } |
| | | try { |
| | | // ä¼å
ä»extraParamsä¸è·å |
| | | if (device.getExtraParams() != null && !device.getExtraParams().trim().isEmpty()) { |
| | | Map<String, Object> extraParams = objectMapper.readValue(device.getExtraParams(), MAP_TYPE); |
| | | Object location = extraParams.get("location"); |
| | | if (location != null) { |
| | | return String.valueOf(location); |
| | | } |
| | | } |
| | | // ä»configJsonä¸è·å |
| | | if (device.getConfigJson() != null && !device.getConfigJson().trim().isEmpty()) { |
| | | Map<String, Object> configJson = objectMapper.readValue(device.getConfigJson(), MAP_TYPE); |
| | | Object location = configJson.get("location"); |
| | | if (location != null) { |
| | | return String.valueOf(location); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("è§£æè®¾å¤ä½ç½®ä¿¡æ¯å¤±è´¥, deviceId={}", device.getId(), e); |
| | | } |
| | | return "é»è®¤ä½ç½®"; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.mes.device.service.impl; |
| | | |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.device.entity.DeviceGroupConfig; |
| | | import com.mes.device.service.DeviceCoordinationService; |
| | | import com.mes.task.model.TaskExecutionContext; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.CollectionUtils; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * 设å¤åè°æå¡å®ç° |
| | | * |
| | | * @author mes |
| | | * @since 2025-01-XX |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class DeviceCoordinationServiceImpl implements DeviceCoordinationService { |
| | | |
| | | private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {}; |
| | | |
| | | private final ObjectMapper objectMapper; |
| | | |
| | | @Override |
| | | public CoordinationResult coordinateExecution(DeviceGroupConfig groupConfig, |
| | | List<DeviceConfig> devices, |
| | | TaskExecutionContext context) { |
| | | if (CollectionUtils.isEmpty(devices)) { |
| | | return CoordinationResult.failure("设å¤å表为空"); |
| | | } |
| | | |
| | | // æ£æ¥ææè®¾å¤çä¾èµå
³ç³» |
| | | List<String> unsatisfiedDevices = new ArrayList<>(); |
| | | for (DeviceConfig device : devices) { |
| | | DependencyCheckResult checkResult = checkDependencies(device, context); |
| | | if (!checkResult.isSatisfied()) { |
| | | unsatisfiedDevices.add(device.getDeviceName() + "(" + device.getDeviceCode() + ")"); |
| | | log.warn("设å¤ä¾èµæ£æ¥å¤±è´¥: deviceId={}, message={}", device.getId(), checkResult.getMessage()); |
| | | } |
| | | } |
| | | |
| | | if (!unsatisfiedDevices.isEmpty()) { |
| | | String message = "以ä¸è®¾å¤çä¾èµæ¡ä»¶ä¸æ»¡è¶³: " + String.join(", ", unsatisfiedDevices); |
| | | return CoordinationResult.failure(message); |
| | | } |
| | | |
| | | // æå»ºåè°å
æ°æ® |
| | | Map<String, Object> metadata = new HashMap<>(); |
| | | metadata.put("deviceCount", devices.size()); |
| | | metadata.put("executionOrder", devices.stream() |
| | | .map(d -> d.getDeviceName() + "(" + d.getDeviceCode() + ")") |
| | | .collect(Collectors.toList())); |
| | | |
| | | return CoordinationResult.success("设å¤åè°æåï¼å¯ä»¥å¼å§æ§è¡", metadata); |
| | | } |
| | | |
| | | @Override |
| | | public boolean transferData(DeviceConfig fromDevice, |
| | | DeviceConfig toDevice, |
| | | Map<String, Object> data, |
| | | TaskExecutionContext context) { |
| | | if (fromDevice == null || toDevice == null || data == null || context == null) { |
| | | log.warn("æ°æ®ä¼ éåæ°ä¸å®æ´"); |
| | | return false; |
| | | } |
| | | |
| | | try { |
| | | // å°æ°æ®åå¨å°å
±äº«ä¸ä¸æä¸ |
| | | String dataKey = String.format("device_%s_to_%s", fromDevice.getId(), toDevice.getId()); |
| | | context.getSharedData().put(dataKey, data); |
| | | |
| | | // æ ¹æ®è®¾å¤ç±»åï¼æåå
³é®æ°æ®å¹¶æ´æ°ä¸ä¸æ |
| | | if (DeviceConfig.DeviceType.LOAD_VEHICLE.equals(fromDevice.getDeviceType())) { |
| | | // ä¸å¤§è½¦è®¾å¤å®æï¼ä¼ éç»çIDå表 |
| | | Object glassIds = data.get("glassIds"); |
| | | if (glassIds instanceof List) { |
| | | @SuppressWarnings("unchecked") |
| | | List<String> ids = (List<String>) glassIds; |
| | | context.setLoadedGlassIds(new ArrayList<>(ids)); |
| | | log.info("ä¸å¤§è½¦è®¾å¤æ°æ®ä¼ é: fromDevice={}, toDevice={}, glassIds={}", |
| | | fromDevice.getDeviceCode(), toDevice.getDeviceCode(), ids); |
| | | } |
| | | } else if (DeviceConfig.DeviceType.LARGE_GLASS.equals(fromDevice.getDeviceType())) { |
| | | // 大çç设å¤å®æï¼ä¼ éå¤çåçç»çIDå表 |
| | | Object glassIds = data.get("glassIds"); |
| | | if (glassIds instanceof List) { |
| | | @SuppressWarnings("unchecked") |
| | | List<String> ids = (List<String>) glassIds; |
| | | context.setProcessedGlassIds(new ArrayList<>(ids)); |
| | | log.info("大ççè®¾å¤æ°æ®ä¼ é: fromDevice={}, toDevice={}, glassIds={}", |
| | | fromDevice.getDeviceCode(), toDevice.getDeviceCode(), ids); |
| | | } |
| | | } |
| | | |
| | | // åå¨éç¨æ°æ® |
| | | context.getSharedData().put("lastTransferFrom", fromDevice.getId()); |
| | | context.getSharedData().put("lastTransferTo", toDevice.getId()); |
| | | context.getSharedData().put("lastTransferTime", System.currentTimeMillis()); |
| | | |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("æ°æ®ä¼ é失败: fromDevice={}, toDevice={}", |
| | | fromDevice.getDeviceCode(), toDevice.getDeviceCode(), e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void syncDeviceStatus(DeviceConfig device, |
| | | DeviceStatus status, |
| | | TaskExecutionContext context) { |
| | | if (device == null || context == null) { |
| | | return; |
| | | } |
| | | |
| | | String statusKey = String.format("device_%s_status", device.getId()); |
| | | context.getSharedData().put(statusKey, status.name()); |
| | | context.getSharedData().put(statusKey + "_time", System.currentTimeMillis()); |
| | | |
| | | // æ´æ°è®¾å¤ç¶ææ å° |
| | | @SuppressWarnings("unchecked") |
| | | Map<Long, String> deviceStatusMap = (Map<Long, String>) context.getSharedData() |
| | | .computeIfAbsent("deviceStatusMap", k -> new HashMap<Long, String>()); |
| | | deviceStatusMap.put(device.getId(), status.name()); |
| | | |
| | | log.debug("设å¤ç¶æåæ¥: deviceId={}, status={}", device.getId(), status); |
| | | } |
| | | |
| | | @Override |
| | | public DependencyCheckResult checkDependencies(DeviceConfig device, |
| | | TaskExecutionContext context) { |
| | | if (device == null || context == null) { |
| | | return DependencyCheckResult.unsatisfied("è®¾å¤æä¸ä¸æä¸ºç©º", Collections.emptyList()); |
| | | } |
| | | |
| | | List<String> missingDependencies = new ArrayList<>(); |
| | | |
| | | // æ£æ¥è®¾å¤ç±»åç¹å®çä¾èµ |
| | | String deviceType = device.getDeviceType(); |
| | | if (DeviceConfig.DeviceType.LARGE_GLASS.equals(deviceType)) { |
| | | // 大çç设å¤éè¦ä¸å¤§è½¦è®¾å¤å
宿 |
| | | List<String> loadedGlassIds = context.getSafeLoadedGlassIds(); |
| | | if (CollectionUtils.isEmpty(loadedGlassIds)) { |
| | | missingDependencies.add("ä¸å¤§è½¦è®¾å¤æªå®æï¼ç¼ºå°ç»çIDå表"); |
| | | } |
| | | } else if (DeviceConfig.DeviceType.GLASS_STORAGE.equals(deviceType)) { |
| | | // ç»çåå¨è®¾å¤éè¦å¤§çç设å¤å
宿ï¼ä¼å
ï¼ï¼æä¸å¤§è½¦è®¾å¤å®æ |
| | | List<String> processedGlassIds = context.getSafeProcessedGlassIds(); |
| | | List<String> loadedGlassIds = context.getSafeLoadedGlassIds(); |
| | | if (CollectionUtils.isEmpty(processedGlassIds) && CollectionUtils.isEmpty(loadedGlassIds)) { |
| | | missingDependencies.add("åç½®è®¾å¤æªå®æï¼ç¼ºå°ç»çIDå表"); |
| | | } |
| | | } |
| | | |
| | | // æ£æ¥è®¾å¤é
ç½®ä¸çä¾èµå
³ç³»ï¼ä»extraParamsä¸è¯»åï¼ |
| | | Map<String, Object> deviceDependencies = getDeviceDependencies(device); |
| | | if (!CollectionUtils.isEmpty(deviceDependencies)) { |
| | | for (Map.Entry<String, Object> entry : deviceDependencies.entrySet()) { |
| | | String depDeviceCode = entry.getKey(); |
| | | Object depStatus = entry.getValue(); |
| | | |
| | | // æ£æ¥ä¾èµè®¾å¤çç¶æ |
| | | @SuppressWarnings("unchecked") |
| | | Map<Long, String> deviceStatusMap = (Map<Long, String>) context.getSharedData() |
| | | .get("deviceStatusMap"); |
| | | |
| | | if (deviceStatusMap != null) { |
| | | // è¿éç®åå¤çï¼å®é
åºè¯¥æ ¹æ®deviceCodeæ¥æ¾è®¾å¤ID |
| | | // ææ¶è·³è¿åºäºè®¾å¤ä»£ç çä¾èµæ£æ¥ |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (missingDependencies.isEmpty()) { |
| | | return DependencyCheckResult.satisfied(); |
| | | } |
| | | |
| | | return DependencyCheckResult.unsatisfied( |
| | | "设å¤ä¾èµæ¡ä»¶ä¸æ»¡è¶³: " + String.join(", ", missingDependencies), |
| | | missingDependencies |
| | | ); |
| | | } |
| | | |
| | | @Override |
| | | public List<DeviceConfig> getDependentDevices(DeviceConfig device, |
| | | DeviceGroupConfig groupConfig) { |
| | | if (device == null || groupConfig == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | // ä»è®¾å¤é
ç½®ä¸è¯»åä¾èµå
³ç³» |
| | | Map<String, Object> deviceDependencies = getDeviceDependencies(device); |
| | | if (CollectionUtils.isEmpty(deviceDependencies)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | // è¿ééè¦æ ¹æ®deviceCodeæ¥æ¾å¯¹åºçDeviceConfig |
| | | // ç®åå®ç°ï¼è¿å空å表 |
| | | // å®é
åºè¯¥æ¥è¯¢è®¾å¤ç»ä¸çææè®¾å¤ï¼ç¶åæ ¹æ®deviceCodeå¹é
|
| | | log.debug("è·å设å¤ä¾èµ: deviceId={}, dependencies={}", device.getId(), deviceDependencies); |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | /** |
| | | * ä»è®¾å¤é
ç½®ä¸è·åä¾èµå
³ç³» |
| | | */ |
| | | private Map<String, Object> getDeviceDependencies(DeviceConfig device) { |
| | | String extraParams = device.getExtraParams(); |
| | | if (!StringUtils.hasText(extraParams)) { |
| | | return Collections.emptyMap(); |
| | | } |
| | | |
| | | try { |
| | | Map<String, Object> extraParamsMap = objectMapper.readValue(extraParams, MAP_TYPE); |
| | | @SuppressWarnings("unchecked") |
| | | Map<String, Object> dependencies = (Map<String, Object>) extraParamsMap.get("dependencies"); |
| | | return dependencies != null ? dependencies : Collections.emptyMap(); |
| | | } catch (Exception e) { |
| | | log.warn("è§£æè®¾å¤ä¾èµå
³ç³»å¤±è´¥, deviceId={}", device.getId(), e); |
| | | return Collections.emptyMap(); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.mes.device.entity.DeviceGroupConfig; |
| | | import com.mes.device.entity.DeviceGroupRelation; |
| | | import com.mes.device.mapper.DeviceGroupConfigMapper; |
| | | import com.mes.device.mapper.DeviceGroupRelationMapper; |
| | | import com.mes.device.service.DeviceGroupConfigService; |
| | | import com.mes.device.vo.DeviceGroupConfigVO; |
| | | import com.mes.device.vo.DeviceGroupVO; |
| | |
| | | public class DeviceGroupConfigServiceImpl extends ServiceImpl<DeviceGroupConfigMapper, DeviceGroupConfig> implements DeviceGroupConfigService { |
| | | |
| | | private final ObjectMapper objectMapper = new ObjectMapper(); |
| | | private final DeviceGroupRelationMapper deviceGroupRelationMapper; |
| | | private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {}; |
| | | |
| | | public DeviceGroupConfigServiceImpl(DeviceGroupRelationMapper deviceGroupRelationMapper) { |
| | | this.deviceGroupRelationMapper = deviceGroupRelationMapper; |
| | | } |
| | | |
| | | @Override |
| | | public boolean createDeviceGroup(DeviceGroupConfig groupConfig) { |
| | |
| | | vo.setGroupType(getGroupTypeName(group.getGroupType())); |
| | | vo.setStatus(getStatusName(group.getStatus())); |
| | | vo.setDeviceCount(getDeviceCountByGroupId(group.getId())); |
| | | vo.setOnlineDeviceCount(getOnlineDeviceCountByGroupId(group.getId())); |
| | | vo.setCreateTime(group.getCreatedTime()); |
| | | vo.setProjectId(group.getProjectId()); |
| | | return vo; |
| | |
| | | |
| | | @Override |
| | | public List<DeviceGroupConfigVO.GroupInfo> getDeviceGroupVOList(Long projectId, Integer groupType, Integer status) { |
| | | // TODO: è¿ééè¦å®ç°VO转æ¢é»è¾ï¼å
æ¬è®¾å¤æ°éç»è®¡ |
| | | List<DeviceGroupConfig> groupList = getDeviceGroupList(projectId, groupType, status); |
| | | |
| | | return groupList.stream().map(group -> { |
| | |
| | | vo.setStatus(getStatusName(group.getStatus())); |
| | | vo.setDeviceCount(getDeviceCountByGroupId(group.getId())); |
| | | vo.setIsEnabled(group.getStatus() != null && group.getStatus() == DeviceGroupConfig.Status.ENABLED); |
| | | vo.setLocation("é»è®¤ä½ç½®"); // TODO: 仿©å±é
ç½®æå
³è表ä¸è·å |
| | | vo.setSupervisor("é»è®¤ç®¡çå"); // TODO: 仿©å±é
ç½®æå
³è表ä¸è·å |
| | | vo.setLocation(extractLocationFromExtraConfig(group)); |
| | | vo.setSupervisor(extractSupervisorFromExtraConfig(group)); |
| | | vo.setCreatedTime(new Date()); |
| | | vo.setUpdatedTime(new Date()); |
| | | vo.setProjectId(group.getProjectId()); |
| | |
| | | |
| | | @Override |
| | | public int getDeviceCountByGroupId(Long groupId) { |
| | | // è¿ééè¦æ¥è¯¢device_group_relation表æ¥è·åè®¾å¤æ°é |
| | | // ç®åå®ç°ï¼å®é
éè¦æ³¨å
¥DeviceGroupRelationMapper |
| | | return 0; // TODO: å®ç°çå®é»è¾ |
| | | if (groupId == null) { |
| | | return 0; |
| | | } |
| | | try { |
| | | LambdaQueryWrapper<DeviceGroupRelation> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(DeviceGroupRelation::getGroupId, groupId) |
| | | .eq(DeviceGroupRelation::getIsDeleted, 0); |
| | | return (int) deviceGroupRelationMapper.selectCount(wrapper); |
| | | } catch (Exception e) { |
| | | log.error("è·å设å¤ç»è®¾å¤æ°é失败, groupId={}", groupId, e); |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è·å设å¤ç»ä¸çå¨çº¿è®¾å¤æ°é |
| | | * |
| | | * @param groupId 设å¤ç»ID |
| | | * @return å¨çº¿è®¾å¤æ°é |
| | | */ |
| | | private int getOnlineDeviceCountByGroupId(Long groupId) { |
| | | if (groupId == null) { |
| | | return 0; |
| | | } |
| | | try { |
| | | Integer count = deviceGroupRelationMapper.getOnlineDeviceCountByGroupId(groupId); |
| | | return count != null ? count : 0; |
| | | } catch (Exception e) { |
| | | log.error("è·å设å¤ç»å¨çº¿è®¾å¤æ°é失败, groupId={}", groupId, e); |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | // è·å设å¤ç»ä¸çè®¾å¤æ°é |
| | | int totalDevices = getDeviceCountByGroupId(groupId); |
| | | |
| | | // è·åå¨çº¿è®¾å¤æ°é |
| | | int activeDevices = 0; // TODO: éè¦æ ¹æ®å®é
ä¸å¡é»è¾å®ç° |
| | | // è·åå¨çº¿è®¾å¤æ°éï¼ç¶æä¸ºæ£å¸¸çè®¾å¤æ°ï¼ |
| | | int activeDevices = getActiveDeviceCountByGroupId(groupId); |
| | | |
| | | // 计ç®å¹³åæ§è½ææ ï¼æ¨¡ææ°æ®ï¼ |
| | | double averageCpuUsage = 45.5; // CPU使ç¨çç¾åæ¯ |
| | |
| | | |
| | | // è·å设å¤ç»ä¸ç设å¤ä¿¡æ¯ |
| | | int totalDevices = getDeviceCountByGroupId(groupId); |
| | | int onlineDevices = 0; // TODO: éè¦æ ¹æ®å®é
ä¸å¡é»è¾å®ç° |
| | | int onlineDevices = getActiveDeviceCountByGroupId(groupId); |
| | | int offlineDevices = totalDevices - onlineDevices; |
| | | |
| | | // 计ç®å¥åº·ç¶æ |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 仿©å±é
ç½®ä¸æåä½ç½®ä¿¡æ¯ |
| | | */ |
| | | private String extractLocationFromExtraConfig(DeviceGroupConfig group) { |
| | | if (group == null || group.getExtraConfig() == null) { |
| | | return "é»è®¤ä½ç½®"; |
| | | } |
| | | try { |
| | | Map<String, Object> extraConfig = objectMapper.readValue(group.getExtraConfig(), MAP_TYPE); |
| | | Object location = extraConfig.get("location"); |
| | | return location != null ? String.valueOf(location) : "é»è®¤ä½ç½®"; |
| | | } catch (Exception e) { |
| | | log.warn("è§£æè®¾å¤ç»ä½ç½®ä¿¡æ¯å¤±è´¥, groupId={}", group.getId(), e); |
| | | return "é»è®¤ä½ç½®"; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 仿©å±é
ç½®ä¸æå管çåä¿¡æ¯ |
| | | */ |
| | | private String extractSupervisorFromExtraConfig(DeviceGroupConfig group) { |
| | | if (group == null || group.getExtraConfig() == null) { |
| | | return "é»è®¤ç®¡çå"; |
| | | } |
| | | try { |
| | | Map<String, Object> extraConfig = objectMapper.readValue(group.getExtraConfig(), MAP_TYPE); |
| | | Object supervisor = extraConfig.get("supervisor"); |
| | | return supervisor != null ? String.valueOf(supervisor) : "é»è®¤ç®¡çå"; |
| | | } catch (Exception e) { |
| | | log.warn("è§£æè®¾å¤ç»ç®¡çåä¿¡æ¯å¤±è´¥, groupId={}", group.getId(), e); |
| | | return "é»è®¤ç®¡çå"; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è·å设å¤ç»ä¸æ´»è·è®¾å¤æ°éï¼ç¶æä¸ºæ£å¸¸ç设å¤ï¼ |
| | | */ |
| | | private int getActiveDeviceCountByGroupId(Long groupId) { |
| | | if (groupId == null) { |
| | | return 0; |
| | | } |
| | | try { |
| | | LambdaQueryWrapper<DeviceGroupRelation> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(DeviceGroupRelation::getGroupId, groupId) |
| | | .eq(DeviceGroupRelation::getStatus, DeviceGroupRelation.Status.NORMAL) |
| | | .eq(DeviceGroupRelation::getIsDeleted, 0); |
| | | return (int) deviceGroupRelationMapper.selectCount(wrapper); |
| | | } catch (Exception e) { |
| | | log.error("è·å设å¤ç»æ´»è·è®¾å¤æ°é失败, groupId={}", groupId, e); |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public java.util.List<String> getAllGroupStatuses() { |
| | | try { |
| | |
| | | import org.springframework.util.CollectionUtils; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | |
| | | private final DeviceGroupRelationService deviceGroupRelationService; |
| | | private final PlcTestWriteService plcTestWriteService; |
| | | private final ObjectMapper objectMapper; |
| | | |
| | | public enum PlcOperationType { |
| | | REQUEST("PLC请æ±", "PLC 请æ±åéæå", "PLC 请æ±åé失败"), |
| | | REPORT("PLCæ±æ¥", "PLC æ±æ¥æ¨¡ææå", "PLC æ±æ¥æ¨¡æå¤±è´¥"), |
| | | RESET("PLCéç½®", "PLC ç¶æå·²éç½®", "PLC ç¶æé置失败"); |
| | | |
| | | private final String display; |
| | | private final String successMsg; |
| | | private final String failedMsg; |
| | | |
| | | PlcOperationType(String display, String successMsg, String failedMsg) { |
| | | this.display = display; |
| | | this.successMsg = successMsg; |
| | | this.failedMsg = failedMsg; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public DevicePlcVO.OperationResult triggerRequest(Long deviceId) { |
| | |
| | | throw new IllegalStateException("æ æ³è§£æè®¾å¤ç PLC é¡¹ç®æ è¯, deviceId=" + device.getId()); |
| | | } |
| | | |
| | | public enum PlcOperationType { |
| | | REQUEST("PLC请æ±", "PLC 请æ±åéæå", "PLC 请æ±åé失败"), |
| | | REPORT("PLCæ±æ¥", "PLC æ±æ¥æ¨¡ææå", "PLC æ±æ¥æ¨¡æå¤±è´¥"), |
| | | RESET("PLCéç½®", "PLC ç¶æå·²éç½®", "PLC ç¶æé置失败"); |
| | | |
| | | private final String display; |
| | | private final String successMsg; |
| | | private final String failedMsg; |
| | | |
| | | PlcOperationType(String display, String successMsg, String failedMsg) { |
| | | this.display = display; |
| | | this.successMsg = successMsg; |
| | | this.failedMsg = failedMsg; |
| | | } |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.mes.device.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.device.entity.DeviceStatus; |
| | | import com.mes.device.mapper.DeviceConfigMapper; |
| | | import com.mes.device.mapper.DeviceStatusMapper; |
| | | import com.mes.device.service.DeviceConfigService; |
| | | import com.mes.device.service.DeviceStatusService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * 设å¤ç¶ææå¡å®ç°ç±» |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class DeviceStatusServiceImpl extends ServiceImpl<DeviceStatusMapper, DeviceStatus> |
| | | implements DeviceStatusService { |
| | | |
| | | private final DeviceConfigService deviceConfigService; |
| | | private final DeviceConfigMapper deviceConfigMapper; |
| | | |
| | | @Override |
| | | public DeviceStatus getLatestByDeviceId(String deviceId) { |
| | | if (deviceId == null || deviceId.trim().isEmpty()) { |
| | | return null; |
| | | } |
| | | try { |
| | | return baseMapper.getLatestByDeviceId(deviceId.trim()); |
| | | } catch (Exception e) { |
| | | log.error("è·å设å¤ç¶æå¤±è´¥, deviceId={}", deviceId, e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<DeviceStatus> getLatestByDeviceIds(List<String> deviceIds) { |
| | | if (deviceIds == null || deviceIds.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | try { |
| | | List<String> validIds = deviceIds.stream() |
| | | .filter(id -> id != null && !id.trim().isEmpty()) |
| | | .map(String::trim) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | if (validIds.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | return baseMapper.getLatestByDeviceIds(validIds); |
| | | } catch (Exception e) { |
| | | log.error("æ¹éè·å设å¤ç¶æå¤±è´¥, deviceIds={}", deviceIds, e); |
| | | return new ArrayList<>(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean updateDeviceOnlineStatus(Long deviceId, String status) { |
| | | if (deviceId == null) { |
| | | log.warn("设å¤IDä¸è½ä¸ºç©º"); |
| | | return false; |
| | | } |
| | | if (status == null || status.trim().isEmpty()) { |
| | | log.warn("设å¤ç¶æä¸è½ä¸ºç©º"); |
| | | return false; |
| | | } |
| | | |
| | | try { |
| | | // è·å设å¤é
ç½® |
| | | DeviceConfig device = deviceConfigService.getDeviceById(deviceId); |
| | | if (device == null) { |
| | | log.warn("设å¤ä¸åå¨: deviceId={}", deviceId); |
| | | return false; |
| | | } |
| | | |
| | | String deviceIdStr = device.getDeviceId(); |
| | | if (deviceIdStr == null || deviceIdStr.trim().isEmpty()) { |
| | | log.warn("设å¤é
ç½®ä¸device_idåæ®µä¸ºç©º: id={}", deviceId); |
| | | return false; |
| | | } |
| | | |
| | | // æ£æ¥æ¯å¦å·²æç¶æè®°å½ |
| | | DeviceStatus existing = getLatestByDeviceId(deviceIdStr); |
| | | Date now = new Date(); |
| | | |
| | | if (existing != null) { |
| | | // æ´æ°ç°æè®°å½ |
| | | existing.setStatus(status); |
| | | existing.setLastHeartbeat(now); |
| | | boolean result = updateById(existing); |
| | | if (result) { |
| | | log.info("æ´æ°è®¾å¤å¨çº¿ç¶ææå: deviceId={}, status={}", deviceId, status); |
| | | } |
| | | return result; |
| | | } else { |
| | | // å建æ°è®°å½ |
| | | DeviceStatus newStatus = new DeviceStatus(); |
| | | newStatus.setDeviceId(deviceIdStr); |
| | | newStatus.setStatus(status); |
| | | newStatus.setLastHeartbeat(now); |
| | | newStatus.setCreatedTime(now); |
| | | boolean result = save(newStatus); |
| | | if (result) { |
| | | log.info("å建设å¤ç¶æè®°å½æå: deviceId={}, status={}", deviceId, status); |
| | | } |
| | | return result; |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("æ´æ°è®¾å¤å¨çº¿ç¶æå¤±è´¥: deviceId={}, status={}", deviceId, status, e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean batchUpdateDeviceOnlineStatus(List<Long> deviceIds, String status) { |
| | | if (deviceIds == null || deviceIds.isEmpty()) { |
| | | log.warn("设å¤IDå表ä¸è½ä¸ºç©º"); |
| | | return false; |
| | | } |
| | | if (status == null || status.trim().isEmpty()) { |
| | | log.warn("设å¤ç¶æä¸è½ä¸ºç©º"); |
| | | return false; |
| | | } |
| | | |
| | | try { |
| | | boolean allSuccess = true; |
| | | for (Long deviceId : deviceIds) { |
| | | if (!updateDeviceOnlineStatus(deviceId, status)) { |
| | | allSuccess = false; |
| | | } |
| | | } |
| | | log.info("æ¹éæ´æ°è®¾å¤å¨çº¿ç¶æå®æ: deviceIds={}, status={}, success={}", |
| | | deviceIds.size(), status, allSuccess); |
| | | return allSuccess; |
| | | } catch (Exception e) { |
| | | log.error("æ¹éæ´æ°è®¾å¤å¨çº¿ç¶æå¤±è´¥: deviceIds={}, status={}", deviceIds, status, e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean recordHeartbeat(String deviceId, String status) { |
| | | if (deviceId == null || deviceId.trim().isEmpty()) { |
| | | return false; |
| | | } |
| | | if (status == null || status.trim().isEmpty()) { |
| | | status = DeviceStatus.Status.ONLINE; // é»è®¤å¨çº¿ |
| | | } |
| | | |
| | | try { |
| | | Date now = new Date(); |
| | | DeviceStatus existing = getLatestByDeviceId(deviceId); |
| | | |
| | | if (existing != null) { |
| | | existing.setStatus(status); |
| | | existing.setLastHeartbeat(now); |
| | | return updateById(existing); |
| | | } else { |
| | | DeviceStatus newStatus = new DeviceStatus(); |
| | | newStatus.setDeviceId(deviceId); |
| | | newStatus.setStatus(status); |
| | | newStatus.setLastHeartbeat(now); |
| | | newStatus.setCreatedTime(now); |
| | | return save(newStatus); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("è®°å½è®¾å¤å¿è·³å¤±è´¥: deviceId={}, status={}", deviceId, status, e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public DeviceStatus getLatestByDeviceConfigId(Long deviceConfigId) { |
| | | if (deviceConfigId == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | DeviceConfig device = deviceConfigService.getDeviceById(deviceConfigId); |
| | | if (device == null || device.getDeviceId() == null) { |
| | | return null; |
| | | } |
| | | return getLatestByDeviceId(device.getDeviceId()); |
| | | } catch (Exception e) { |
| | | log.error("æ ¹æ®è®¾å¤é
ç½®IDè·å设å¤ç¶æå¤±è´¥: deviceConfigId={}", deviceConfigId, e); |
| | | return null; |
| | | } |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.mes.device.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.mes.device.entity.GlassInfo; |
| | | import com.mes.device.mapper.DeviceGlassInfoMapper; |
| | | import com.mes.device.service.GlassInfoService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * ç»çä¿¡æ¯æå¡å®ç°ç±» |
| | | * |
| | | * @author mes |
| | | * @since 2024-11-20 |
| | | */ |
| | | @Slf4j |
| | | @Service("deviceGlassInfoService") |
| | | public class GlassInfoServiceImpl extends ServiceImpl<DeviceGlassInfoMapper, GlassInfo> implements GlassInfoService { |
| | | |
| | | @Override |
| | | public GlassInfo getGlassInfo(String glassId) { |
| | | if (glassId == null || glassId.trim().isEmpty()) { |
| | | return null; |
| | | } |
| | | try { |
| | | return baseMapper.selectByGlassId(glassId.trim()); |
| | | } catch (Exception e) { |
| | | log.error("æ¥è¯¢ç»çä¿¡æ¯å¤±è´¥, glassId={}", glassId, e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Integer getGlassLength(String glassId) { |
| | | GlassInfo glassInfo = getGlassInfo(glassId); |
| | | return glassInfo != null ? glassInfo.getGlassLength() : null; |
| | | } |
| | | |
| | | @Override |
| | | public List<GlassInfo> getGlassInfos(List<String> glassIds) { |
| | | if (glassIds == null || glassIds.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | try { |
| | | // è¿æ»¤ç©ºå¼å¹¶å»é |
| | | List<String> validIds = glassIds.stream() |
| | | .filter(id -> id != null && !id.trim().isEmpty()) |
| | | .map(String::trim) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (validIds.isEmpty()) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | return baseMapper.selectByGlassIds(validIds); |
| | | } catch (Exception e) { |
| | | log.error("æ¹éæ¥è¯¢ç»çä¿¡æ¯å¤±è´¥, glassIds={}", glassIds, e); |
| | | return Collections.emptyList(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Integer> getGlassLengthMap(List<String> glassIds) { |
| | | Map<String, Integer> lengthMap = new HashMap<>(); |
| | | |
| | | if (glassIds == null || glassIds.isEmpty()) { |
| | | return lengthMap; |
| | | } |
| | | |
| | | try { |
| | | List<GlassInfo> glassInfos = getGlassInfos(glassIds); |
| | | for (GlassInfo glassInfo : glassInfos) { |
| | | if (glassInfo.getGlassId() != null && glassInfo.getGlassLength() != null) { |
| | | lengthMap.put(glassInfo.getGlassId(), glassInfo.getGlassLength()); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("è·åç»çé¿åº¦æ å°å¤±è´¥, glassIds={}", glassIds, e); |
| | | } |
| | | |
| | | return lengthMap; |
| | | } |
| | | |
| | | @Override |
| | | public boolean saveOrUpdateGlassInfo(GlassInfo glassInfo) { |
| | | if (glassInfo == null || glassInfo.getGlassId() == null) { |
| | | return false; |
| | | } |
| | | try { |
| | | // æ£æ¥æ¯å¦å·²åå¨ |
| | | GlassInfo existing = baseMapper.selectByGlassId(glassInfo.getGlassId()); |
| | | if (existing != null) { |
| | | glassInfo.setId(existing.getId()); |
| | | return updateById(glassInfo); |
| | | } else { |
| | | return save(glassInfo); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("ä¿åææ´æ°ç»çä¿¡æ¯å¤±è´¥, glassInfo={}", glassInfo, e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean batchSaveOrUpdateGlassInfo(List<GlassInfo> glassInfos) { |
| | | if (glassInfos == null || glassInfos.isEmpty()) { |
| | | return true; |
| | | } |
| | | try { |
| | | for (GlassInfo glassInfo : glassInfos) { |
| | | saveOrUpdateGlassInfo(glassInfo); |
| | | } |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("æ¹éä¿åææ´æ°ç»çä¿¡æ¯å¤±è´¥", e); |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | private String deviceName; |
| | | private String deviceCode; |
| | | private String deviceType; |
| | | private String plcIp; |
| | | private String deviceRole; |
| | | private String status; |
| | | private Date lastHeartbeat; |
| | |
| | | private String groupType; |
| | | private String status; |
| | | private Integer deviceCount; |
| | | private Integer onlineDeviceCount; |
| | | private Date createTime; |
| | | private Long projectId; |
| | | } |
| | |
| | | |
| | | @Override |
| | | public InteractionResult execute(InteractionContext context) { |
| | | List<String> processed = context.getProcessedGlassIds(); |
| | | if (CollectionUtils.isEmpty(processed)) { |
| | | return InteractionResult.waitResult("没æå¯åå¨çç»ç", null); |
| | | } |
| | | try { |
| | | // åç½®æ¡ä»¶éªè¯ |
| | | if (context.getCurrentDevice() == null) { |
| | | return InteractionResult.fail("设å¤é
ç½®ä¸åå¨"); |
| | | } |
| | | |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("storedCount", processed.size()); |
| | | data.put("storedGlasses", processed); |
| | | return InteractionResult.success(data); |
| | | // ä¼å
使ç¨å¤çåçç»çIDï¼å¦ææ²¡æå使ç¨ä¸å¤§è½¦çç»çID |
| | | List<String> processed = context.getProcessedGlassIds(); |
| | | if (CollectionUtils.isEmpty(processed)) { |
| | | processed = context.getLoadedGlassIds(); |
| | | if (CollectionUtils.isEmpty(processed)) { |
| | | // å°è¯ä»å
±äº«æ°æ®è·å |
| | | Object processedGlasses = context.getSharedData().get("processedGlasses"); |
| | | if (processedGlasses instanceof List) { |
| | | @SuppressWarnings("unchecked") |
| | | List<String> list = (List<String>) processedGlasses; |
| | | processed = list; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (CollectionUtils.isEmpty(processed)) { |
| | | return InteractionResult.waitResult("没æå¯åå¨çç»ç", null); |
| | | } |
| | | |
| | | // éªè¯ç»çID |
| | | for (String glassId : processed) { |
| | | if (glassId == null || glassId.trim().isEmpty()) { |
| | | return InteractionResult.fail("ç»çIDä¸è½ä¸ºç©º"); |
| | | } |
| | | } |
| | | |
| | | // æ§è¡åå¨æä½ |
| | | context.getSharedData().put("storedGlasses", processed); |
| | | context.getSharedData().put("storageTime", System.currentTimeMillis()); |
| | | |
| | | // åç½®æ¡ä»¶æ£æ¥ |
| | | Object stored = context.getSharedData().get("storedGlasses"); |
| | | if (stored == null) { |
| | | return InteractionResult.fail("ç»çåå¨å¤±è´¥ï¼å卿°æ®ä¸ºç©º"); |
| | | } |
| | | |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("storedCount", processed.size()); |
| | | data.put("storedGlasses", processed); |
| | | data.put("deviceId", context.getCurrentDevice().getId()); |
| | | data.put("deviceCode", context.getCurrentDevice().getDeviceCode()); |
| | | return InteractionResult.success(data); |
| | | } catch (Exception e) { |
| | | return InteractionResult.fail("ç»çåå¨äº¤äºæ§è¡å¼å¸¸: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | @Override |
| | | public InteractionResult execute(InteractionContext context) { |
| | | Object source = context.getSharedData().get("glassesFromVehicle"); |
| | | List<String> glassQueue = castList(source); |
| | | if (CollectionUtils.isEmpty(glassQueue)) { |
| | | return InteractionResult.waitResult("çå¾
ä¸å¤§è½¦è¾åº", null); |
| | | try { |
| | | // åç½®æ¡ä»¶éªè¯ |
| | | if (context.getCurrentDevice() == null) { |
| | | return InteractionResult.fail("设å¤é
ç½®ä¸åå¨"); |
| | | } |
| | | |
| | | // æ£æ¥ä¸å¤§è½¦æ¯å¦å®æ |
| | | Object source = context.getSharedData().get("glassesFromVehicle"); |
| | | List<String> glassQueue = castList(source); |
| | | if (CollectionUtils.isEmpty(glassQueue)) { |
| | | // ä¹å°è¯ä»ä¸ä¸æè·å |
| | | glassQueue = context.getLoadedGlassIds(); |
| | | if (CollectionUtils.isEmpty(glassQueue)) { |
| | | return InteractionResult.waitResult("çå¾
ä¸å¤§è½¦è¾åº", null); |
| | | } |
| | | } |
| | | |
| | | // éªè¯ç»çID |
| | | for (String glassId : glassQueue) { |
| | | if (glassId == null || glassId.trim().isEmpty()) { |
| | | return InteractionResult.fail("ç»çIDä¸è½ä¸ºç©º"); |
| | | } |
| | | } |
| | | |
| | | // æ§è¡å¤§ççå¤ç |
| | | List<String> processed = new ArrayList<>(glassQueue); |
| | | context.setProcessedGlassIds(processed); |
| | | context.getSharedData().put("processedGlasses", processed); |
| | | context.getSharedData().put("largeGlassProcessTime", System.currentTimeMillis()); |
| | | |
| | | // åç½®æ¡ä»¶æ£æ¥ |
| | | if (context.getProcessedGlassIds().isEmpty()) { |
| | | return InteractionResult.fail("大ççå¤ç失败ï¼å¤çåçç»çIDå表为空"); |
| | | } |
| | | |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("processedCount", processed.size()); |
| | | data.put("processedGlasses", processed); |
| | | data.put("deviceId", context.getCurrentDevice().getId()); |
| | | data.put("deviceCode", context.getCurrentDevice().getDeviceCode()); |
| | | return InteractionResult.success(data); |
| | | } catch (Exception e) { |
| | | return InteractionResult.fail("大ççäº¤äºæ§è¡å¼å¸¸: " + e.getMessage()); |
| | | } |
| | | |
| | | List<String> processed = new ArrayList<>(glassQueue); |
| | | context.setProcessedGlassIds(processed); |
| | | context.getSharedData().put("processedGlasses", processed); |
| | | |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("processedCount", processed.size()); |
| | | data.put("processedGlasses", processed); |
| | | return InteractionResult.success(data); |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | |
| | | package com.mes.interaction.flow; |
| | | |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.device.service.DeviceInteractionService; |
| | | import com.mes.device.vo.DevicePlcVO; |
| | | import com.mes.interaction.DeviceInteraction; |
| | | import com.mes.interaction.base.InteractionContext; |
| | | import com.mes.interaction.base.InteractionResult; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.CollectionUtils; |
| | | |
| | |
| | | * ä¸å¤§è½¦äº¤äºå®ç° |
| | | */ |
| | | @Component |
| | | @RequiredArgsConstructor |
| | | public class LoadVehicleInteraction implements DeviceInteraction { |
| | | |
| | | private final DeviceInteractionService deviceInteractionService; |
| | | |
| | | @Override |
| | | public String getDeviceType() { |
| | |
| | | |
| | | @Override |
| | | public InteractionResult execute(InteractionContext context) { |
| | | List<String> glassIds = context.getParameters().getGlassIds(); |
| | | if (CollectionUtils.isEmpty(glassIds)) { |
| | | return InteractionResult.waitResult("æªæä¾ç»çIDï¼çå¾
è¾å
¥", null); |
| | | try { |
| | | // åç½®æ¡ä»¶éªè¯ |
| | | if (context.getCurrentDevice() == null) { |
| | | return InteractionResult.fail("设å¤é
ç½®ä¸åå¨"); |
| | | } |
| | | |
| | | List<String> glassIds = context.getParameters().getGlassIds(); |
| | | if (CollectionUtils.isEmpty(glassIds)) { |
| | | return InteractionResult.waitResult("æªæä¾ç»çIDï¼çå¾
è¾å
¥", null); |
| | | } |
| | | |
| | | // éªè¯ç»çIDæ ¼å¼ |
| | | for (String glassId : glassIds) { |
| | | if (glassId == null || glassId.trim().isEmpty()) { |
| | | return InteractionResult.fail("ç»çIDä¸è½ä¸ºç©º"); |
| | | } |
| | | } |
| | | |
| | | // æå»ºPLCåå
¥åæ° |
| | | Map<String, Object> params = new HashMap<>(); |
| | | params.put("glassIds", glassIds); |
| | | params.put("positionCode", context.getParameters().getPositionCode()); |
| | | params.put("positionValue", context.getParameters().getPositionValue()); |
| | | params.put("triggerRequest", true); |
| | | |
| | | // æ§è¡å®é
çPLCåå
¥æä½ |
| | | DevicePlcVO.OperationResult plcResult = deviceInteractionService.executeOperation( |
| | | context.getCurrentDevice().getId(), |
| | | "feedGlass", |
| | | params |
| | | ); |
| | | |
| | | // æ£æ¥PLCåå
¥ç»æ |
| | | if (plcResult == null || !Boolean.TRUE.equals(plcResult.getSuccess())) { |
| | | String errorMsg = plcResult != null ? plcResult.getMessage() : "PLCåå
¥æä½è¿åç©ºç»æ"; |
| | | return InteractionResult.fail("PLCåå
¥å¤±è´¥: " + errorMsg); |
| | | } |
| | | |
| | | // æ§è¡ä¸å¤§è½¦æä½ï¼æ°æ®æµè½¬ï¼ |
| | | List<String> copied = new ArrayList<>(glassIds); |
| | | context.setLoadedGlassIds(copied); |
| | | context.getSharedData().put("glassesFromVehicle", copied); |
| | | context.getSharedData().put("loadVehicleTime", System.currentTimeMillis()); |
| | | |
| | | // åç½®æ¡ä»¶æ£æ¥ |
| | | if (context.getLoadedGlassIds().isEmpty()) { |
| | | return InteractionResult.fail("ä¸å¤§è½¦æä½å¤±è´¥ï¼ç»çIDå表为空"); |
| | | } |
| | | |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("loaded", copied); |
| | | data.put("glassCount", copied.size()); |
| | | data.put("deviceId", context.getCurrentDevice().getId()); |
| | | data.put("deviceCode", context.getCurrentDevice().getDeviceCode()); |
| | | data.put("plcResult", plcResult.getMessage()); |
| | | return InteractionResult.success(data); |
| | | } catch (Exception e) { |
| | | return InteractionResult.fail("ä¸å¤§è½¦äº¤äºæ§è¡å¼å¸¸: " + e.getMessage()); |
| | | } |
| | | |
| | | List<String> copied = new ArrayList<>(glassIds); |
| | | context.setLoadedGlassIds(copied); |
| | | context.getSharedData().put("glassesFromVehicle", copied); |
| | | |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("loaded", copied); |
| | | data.put("glassCount", copied.size()); |
| | | return InteractionResult.success(data); |
| | | } |
| | | } |
| | | |
| | |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.interaction.BaseDeviceLogicHandler; |
| | | import com.mes.device.service.DevicePlcOperationService; |
| | | import com.mes.device.service.GlassInfoService; |
| | | import com.mes.device.vo.DevicePlcVO; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.ArrayList; |
| | |
| | | @Component |
| | | public class LoadVehicleLogicHandler extends BaseDeviceLogicHandler { |
| | | |
| | | public LoadVehicleLogicHandler(DevicePlcOperationService devicePlcOperationService) { |
| | | private final GlassInfoService glassInfoService; |
| | | |
| | | public LoadVehicleLogicHandler( |
| | | DevicePlcOperationService devicePlcOperationService, |
| | | @Qualifier("deviceGlassInfoService") GlassInfoService glassInfoService) { |
| | | super(devicePlcOperationService); |
| | | this.glassInfoService = glassInfoService; |
| | | } |
| | | |
| | | @Override |
| | |
| | | return handleTriggerReport(deviceConfig, params, logicParams); |
| | | case "reset": |
| | | return handleReset(deviceConfig, params, logicParams); |
| | | case "clearGlass": |
| | | case "clearPlc": |
| | | case "clear": |
| | | return handleClearGlass(deviceConfig, params, logicParams); |
| | | default: |
| | | log.warn("䏿¯æçæä½ç±»å: {}", operation); |
| | | return DevicePlcVO.OperationResult.builder() |
| | |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * æ¸
空PLCä¸çç»çæ°æ® |
| | | */ |
| | | private DevicePlcVO.OperationResult handleClearGlass( |
| | | DeviceConfig deviceConfig, |
| | | Map<String, Object> params, |
| | | Map<String, Object> logicParams) { |
| | | |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | |
| | | int slotCount = getLogicParam(logicParams, "glassSlotCount", 6); |
| | | if (slotCount <= 0) { |
| | | slotCount = 6; |
| | | } |
| | | |
| | | List<String> slotFields = resolveGlassSlotFields(logicParams, slotCount); |
| | | for (String field : slotFields) { |
| | | payload.put(field, ""); |
| | | } |
| | | |
| | | payload.put("plcGlassCount", 0); |
| | | payload.put("plcRequest", 0); |
| | | payload.put("plcReport", 0); |
| | | |
| | | if (params != null && params.containsKey("positionValue")) { |
| | | payload.put("inPosition", params.get("positionValue")); |
| | | } else if (params != null && Boolean.TRUE.equals(params.get("clearPosition"))) { |
| | | payload.put("inPosition", 0); |
| | | } |
| | | |
| | | log.info("æ¸
空ä¸å¤§è½¦PLCç»çæ°æ®: deviceId={}, clearedSlots={}", deviceConfig.getId(), slotFields.size()); |
| | | |
| | | return devicePlcOperationService.writeFields( |
| | | deviceConfig.getId(), |
| | | payload, |
| | | "ä¸å¤§è½¦-æ¸
空ç»çæ°æ®" |
| | | ); |
| | | } |
| | | |
| | | private List<String> resolveGlassSlotFields(Map<String, Object> logicParams, int fallbackCount) { |
| | | List<String> fields = new ArrayList<>(); |
| | | if (logicParams != null) { |
| | | Object slotFieldConfig = logicParams.get("glassSlotFields"); |
| | | if (slotFieldConfig instanceof List) { |
| | | List<?> configured = (List<?>) slotFieldConfig; |
| | | for (Object item : configured) { |
| | | if (item != null) { |
| | | String fieldName = String.valueOf(item).trim(); |
| | | if (!fieldName.isEmpty()) { |
| | | fields.add(fieldName); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (fields.isEmpty()) { |
| | | for (int i = 1; i <= fallbackCount; i++) { |
| | | fields.add("plcGlassId" + i); |
| | | } |
| | | } |
| | | return fields; |
| | | } |
| | | |
| | | @Override |
| | | public String validateLogicParams(DeviceConfig deviceConfig) { |
| | | Map<String, Object> logicParams = parseLogicParams(deviceConfig); |
| | |
| | | |
| | | if (result.isEmpty()) { |
| | | List<String> glassIds = (List<String>) params.get("glassIds"); |
| | | if (glassIds != null) { |
| | | if (glassIds != null && !glassIds.isEmpty()) { |
| | | // 仿°æ®åºæ¥è¯¢ç»ç尺寸 |
| | | Map<String, Integer> lengthMap = glassInfoService.getGlassLengthMap(glassIds); |
| | | for (String glassId : glassIds) { |
| | | result.add(new GlassInfo(glassId, null)); |
| | | Integer length = lengthMap.get(glassId); |
| | | result.add(new GlassInfo(glassId, length)); |
| | | } |
| | | log.debug("仿°æ®åºæ¥è¯¢ç»ç尺寸: glassIds={}, lengthMap={}", glassIds, lengthMap); |
| | | } |
| | | } |
| | | return result; |
| | |
| | | import com.github.xingshuangs.iot.common.enums.EDataType; |
| | | import com.github.xingshuangs.iot.protocol.s7.serializer.S7Parameter; |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.entity.PlcAddress; |
| | | import com.mes.s7.enhanced.EnhancedS7Serializer; |
| | | |
| | | import java.util.ArrayList; |
| | |
| | | |
| | | /** |
| | | * PLCå¨ææ°æ®è¯»åæå¡ |
| | | * æ ¹æ®PlcAddressé
ç½®å¨ææå»ºåæ°ï¼æ¯æä»»æå段ç»åçPLCæ°æ®äº¤äº |
| | | * æ ¹æ®DeviceConfigé
ç½®å¨ææå»ºåæ°ï¼æ¯æä»»æå段ç»åçPLCæ°æ®äº¤äº |
| | | * |
| | | * @author huang |
| | | * @date 2025/11/05 |
| | | */ |
| | | public interface PlcDynamicDataService { |
| | | |
| | | /** |
| | | * æ ¹æ®PlcAddressé
ç½®ååæ®µå称读åPLCæ°æ® |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param fieldNames è¦è¯»åçåæ®µåç§°å表 |
| | | * @param s7Serializer S7åºååå¨ |
| | | * @return åæ®µå->å¼ çMap |
| | | */ |
| | | Map<String, Object> readPlcData(PlcAddress config, List<String> fieldNames, EnhancedS7Serializer s7Serializer); |
| | | |
| | | /** |
| | | * æ ¹æ®PlcAddressé
ç½®åæ°æ®Mapåå
¥PLC |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param dataMap åæ®µå->å¼ çMap |
| | | * @param s7Serializer S7åºååå¨ |
| | | */ |
| | | void writePlcData(PlcAddress config, Map<String, Object> dataMap, EnhancedS7Serializer s7Serializer); |
| | | |
| | | /** |
| | | * 读åPLCææå段 |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param s7Serializer S7åºååå¨ |
| | | * @return ææå段çå¼ |
| | | */ |
| | | Map<String, Object> readAllPlcData(PlcAddress config, EnhancedS7Serializer s7Serializer); |
| | | |
| | | /** |
| | | * 读ååä¸ªåæ®µ |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param fieldName åæ®µå |
| | | * @param s7Serializer S7åºååå¨ |
| | | * @return åæ®µå¼ |
| | | */ |
| | | Object readPlcField(PlcAddress config, String fieldName, EnhancedS7Serializer s7Serializer); |
| | | |
| | | /** |
| | | * åå
¥åä¸ªåæ®µ |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param fieldName åæ®µå |
| | | * @param value åæ®µå¼ |
| | | * @param s7Serializer S7åºååå¨ |
| | | */ |
| | | void writePlcField(PlcAddress config, String fieldName, Object value, EnhancedS7Serializer s7Serializer); |
| | | |
| | | /** |
| | | * æ ¹æ®DeviceConfigé
ç½®ååæ®µå称读åPLCæ°æ® |
| | |
| | | * @param s7Serializer S7åºååå¨ |
| | | */ |
| | | void writePlcField(DeviceConfig device, String fieldName, Object value, EnhancedS7Serializer s7Serializer); |
| | | |
| | | /** |
| | | * æ ¹æ®å®ä½ç±»åDeviceConfigé
ç½®åå
¥PLCæ°æ® |
| | | * å®ä½ç±»å段使ç¨@S7Variable注解ï¼addressåæ®µä¸ºå段åï¼å¯¹åºconfigJsonä¸çparamKeyï¼ |
| | | * åç§»éä»configJsonä¸çparamValueè·å |
| | | * |
| | | * @param <T> å®ä½ç±»å |
| | | * @param device 设å¤é
ç½® |
| | | * @param entity å®ä½å¯¹è±¡ |
| | | * @param s7Serializer S7åºååå¨ |
| | | */ |
| | | <T> void writePlcDataByEntity(DeviceConfig device, T entity, EnhancedS7Serializer s7Serializer); |
| | | } |
| | |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.device.service.DeviceConfigService; |
| | | import com.mes.device.util.ConfigJsonHelper; |
| | | import com.mes.entity.PlcBaseData; |
| | | import com.mes.entity.PlcAddress; |
| | | import com.mes.service.PlcDynamicDataService; |
| | | import com.mes.s7.enhanced.EnhancedS7Serializer; |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.ConcurrentMap; |
| | | |
| | | /** |
| | | * PLCæµè¯åå
¥æå¡ |
| | | * 模æPLCè¡ä¸ºï¼åPLCåå
¥æµè¯æ°æ®ï¼ç¨äºæµè¯MESç¨åº |
| | | * |
| | | * åºäºDeviceConfigçæ°APIï¼ç¨äºæ¨¡æPLCè¡ä¸ºè¿è¡æµè¯ |
| | | * |
| | | * æ¨è使ç¨ï¼DevicePlcOperationServiceï¼ç产ç¯å¢ï¼ |
| | | * |
| | | * @author huang |
| | | * @date 2025/10/29 |
| | |
| | | @Service |
| | | public class PlcTestWriteService { |
| | | |
| | | @Resource |
| | | private PlcAddressService plcAddressService; |
| | | |
| | | @Resource |
| | | private DeviceConfigService deviceConfigService; |
| | | |
| | |
| | | private static final int ON = 1; |
| | | private static final int OFF = 0; |
| | | |
| | | // å½å使ç¨çé¡¹ç®æ è¯ |
| | | private String currentProjectId = "vertical"; |
| | | |
| | | // ç¼åä¸å项ç®çS7Serializerå®ä¾ |
| | | // ç¼åä¸å设å¤çS7Serializerå®ä¾ |
| | | private final ConcurrentMap<String, EnhancedS7Serializer> serializerCache = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * 模æPLCåé请æ±åï¼è§¦åMESä»»å¡ä¸åï¼ |
| | | */ |
| | | public boolean simulatePlcRequest() { |
| | | return simulatePlcRequest(currentProjectId); |
| | | } |
| | | |
| | | /** |
| | | * 模æPLCåé请æ±åï¼è§¦åMESä»»å¡ä¸åï¼- æ¯ææå®é¡¹ç® |
| | | */ |
| | | public boolean simulatePlcRequest(String projectId) { |
| | | try { |
| | | PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId); |
| | | if (config == null) { |
| | | log.error("项ç®é
ç½®ä¸åå¨: projectId={}", projectId); |
| | | return false; |
| | | } |
| | | EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config); |
| | | if (s7Serializer == null) { |
| | | log.error("æ æ³å建S7Serializer: projectId={}", projectId); |
| | | return false; |
| | | } |
| | | return simulatePlcRequestInternal(projectId, config, s7Serializer); |
| | | } catch (Exception e) { |
| | | log.error("模æPLC请æ±å失败", e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private boolean simulatePlcRequestInternal(String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception { |
| | | PlcBaseData currentData = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex()); |
| | | if (currentData == null) { |
| | | log.error("读åPLCæ°æ®å¤±è´¥ï¼è¿ånull: projectId={}, dbArea={}, beginIndex={}", |
| | | projectId, config.getDbArea(), config.getBeginIndex()); |
| | | return false; |
| | | } |
| | | |
| | | if (currentData.getOnlineState() == OFF) { |
| | | log.info("å½åPLCèæºæ¨¡å¼ä¸º0ï¼åæ¢èæº"); |
| | | return false; |
| | | } else if (currentData.getPlcReport() == ON) { |
| | | log.info("å½åä¸çPLCæ±æ¥å为1ï¼é置为0"); |
| | | currentData.setPlcReport(OFF); |
| | | } |
| | | currentData.setPlcRequest(ON); |
| | | s7Serializer.write(currentData, config.getDbArea(), config.getBeginIndex()); |
| | | log.info("模æPLCåé请æ±åæåï¼plcRequest=1, projectId={}, dbArea={}, beginIndex={}", |
| | | projectId, config.getDbArea(), config.getBeginIndex()); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 模æPLCä»»å¡å®ææ±æ¥ |
| | | */ |
| | | public boolean simulatePlcReport() { |
| | | return simulatePlcReport(currentProjectId); |
| | | } |
| | | |
| | | /** |
| | | * 模æPLCä»»å¡å®ææ±æ¥ - æ¯ææå®é¡¹ç® |
| | | */ |
| | | public boolean simulatePlcReport(String projectId) { |
| | | try { |
| | | // è·å项ç®é
ç½®ï¼æ°æ®åºå®ä½ï¼ |
| | | PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId); |
| | | if (config == null) { |
| | | log.error("项ç®é
ç½®ä¸åå¨: projectId={}", projectId); |
| | | return false; |
| | | } |
| | | |
| | | // è·å对åºçS7Serializer |
| | | EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config); |
| | | if (s7Serializer == null) { |
| | | log.error("æ æ³å建S7Serializer: projectId={}", projectId); |
| | | return false; |
| | | } |
| | | |
| | | return simulatePlcReportInternal(projectId, config, s7Serializer); |
| | | } catch (Exception e) { |
| | | log.error("模æPLCä»»å¡å®ææ±æ¥å¤±è´¥", e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private boolean simulatePlcReportInternal(String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception { |
| | | PlcBaseData currentData = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex()); |
| | | if (currentData == null) { |
| | | log.error("读åPLCæ°æ®å¤±è´¥ï¼è¿ånull: projectId={}, dbArea={}, beginIndex={}", |
| | | projectId, config.getDbArea(), config.getBeginIndex()); |
| | | return false; |
| | | } |
| | | |
| | | currentData.setPlcReport(ON); |
| | | currentData.setPlcRequest(OFF); |
| | | currentData.setMesGlassCount(10); |
| | | |
| | | s7Serializer.write(currentData, config.getDbArea(), config.getBeginIndex()); |
| | | log.info("模æPLCä»»å¡å®ææ±æ¥ï¼plcReport=1, mesGlassCount=10, projectId={}, dbArea={}, beginIndex={}", |
| | | projectId, config.getDbArea(), config.getBeginIndex()); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 模æPLCåéèæºç¶æ |
| | | */ |
| | | public boolean simulateOnlineStatus(int onlineState) { |
| | | return simulateOnlineStatus(onlineState, currentProjectId); |
| | | } |
| | | |
| | | /** |
| | | * 模æPLCåéèæºç¶æ - æ¯ææå®é¡¹ç® |
| | | */ |
| | | public boolean simulateOnlineStatus(int onlineState, String projectId) { |
| | | try { |
| | | // è·å项ç®é
ç½®ï¼æ°æ®åºå®ä½ï¼ |
| | | PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId); |
| | | if (config == null) { |
| | | log.error("项ç®é
ç½®ä¸åå¨: projectId={}", projectId); |
| | | return false; |
| | | } |
| | | |
| | | // è·å对åºçS7Serializer |
| | | EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config); |
| | | if (s7Serializer == null) { |
| | | log.error("æ æ³å建S7Serializer: projectId={}", projectId); |
| | | return false; |
| | | } |
| | | |
| | | return simulateOnlineStatusInternal(onlineState, projectId, config, s7Serializer); |
| | | } catch (Exception e) { |
| | | log.error("模æPLCèæºç¶æå¤±è´¥", e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private boolean simulateOnlineStatusInternal(int onlineState, String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception { |
| | | PlcBaseData currentData = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex()); |
| | | if (currentData == null) { |
| | | log.error("读åPLCæ°æ®å¤±è´¥ï¼è¿ånull: projectId={}, dbArea={}, beginIndex={}", |
| | | projectId, config.getDbArea(), config.getBeginIndex()); |
| | | return false; |
| | | } |
| | | |
| | | currentData.setOnlineState(onlineState); |
| | | s7Serializer.write(currentData, config.getDbArea(), config.getBeginIndex()); |
| | | log.info("模æPLCèæºç¶æï¼onlineState={}, projectId={}, dbArea={}, beginIndex={}", |
| | | onlineState, projectId, config.getDbArea(), config.getBeginIndex()); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * éç½®PLCææç¶æ |
| | | */ |
| | | public boolean resetPlc() { |
| | | return resetPlc(currentProjectId); |
| | | } |
| | | |
| | | /** |
| | | * éç½®PLCææç¶æ - æ¯ææå®é¡¹ç® |
| | | */ |
| | | public boolean resetPlc(String projectId) { |
| | | try { |
| | | // è·å项ç®é
ç½®ï¼æ°æ®åºå®ä½ï¼ |
| | | PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId); |
| | | if (config == null) { |
| | | log.error("项ç®é
ç½®ä¸åå¨: projectId={}", projectId); |
| | | return false; |
| | | } |
| | | |
| | | EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config); |
| | | if (s7Serializer == null) { |
| | | log.error("æ æ³å建S7Serializer: projectId={}", projectId); |
| | | return false; |
| | | } |
| | | return resetPlcInternal(projectId, config, s7Serializer); |
| | | } catch (Exception e) { |
| | | log.error("éç½®PLCç¶æå¤±è´¥", e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private boolean resetPlcInternal(String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception { |
| | | PlcBaseData resetData = new PlcBaseData(); |
| | | resetData.setPlcRequest(OFF); |
| | | resetData.setPlcReport(OFF); |
| | | resetData.setMesSend(OFF); |
| | | resetData.setMesConfirm(OFF); |
| | | resetData.setOnlineState(ON); |
| | | resetData.setMesGlassCount(0); |
| | | resetData.setAlarmInfo(OFF); |
| | | |
| | | s7Serializer.write(resetData, config.getDbArea(), config.getBeginIndex()); |
| | | log.info("PLCç¶æå·²éç½®, projectId={}, dbArea={}, beginIndex={}", |
| | | projectId, config.getDbArea(), config.getBeginIndex()); |
| | | return true; |
| | | } |
| | | |
| | | /** |
| | | * 读åPLCå½åç¶æ |
| | | */ |
| | | public PlcBaseData readPlcStatus() { |
| | | return readPlcStatus(currentProjectId); |
| | | } |
| | | |
| | | /** |
| | | * 读åPLCå½åç¶æ - æ¯ææå®é¡¹ç® |
| | | */ |
| | | public PlcBaseData readPlcStatus(String projectId) { |
| | | try { |
| | | // è·å项ç®é
ç½®ï¼æ°æ®åºå®ä½ï¼ |
| | | PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId); |
| | | if (config == null) { |
| | | log.error("项ç®é
ç½®ä¸åå¨: projectId={}", projectId); |
| | | return null; |
| | | } |
| | | |
| | | EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config); |
| | | if (s7Serializer == null) { |
| | | log.error("æ æ³å建S7Serializer: projectId={}", projectId); |
| | | return null; |
| | | } |
| | | |
| | | return readPlcStatusInternal(projectId, config, s7Serializer); |
| | | } catch (Exception e) { |
| | | log.error("读åPLCç¶æå¤±è´¥", e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private PlcBaseData readPlcStatusInternal(String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception { |
| | | PlcBaseData data = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex()); |
| | | if (data == null) { |
| | | log.error("读åPLCç¶æè¿ånull: projectId={}, dbArea={}, beginIndex={}", |
| | | projectId, config.getDbArea(), config.getBeginIndex()); |
| | | } |
| | | return data; |
| | | } |
| | | |
| | | /** |
| | | * 设置å½åé¡¹ç®æ è¯ |
| | | */ |
| | | public void setCurrentProjectId(String projectId) { |
| | | this.currentProjectId = projectId; |
| | | } |
| | | |
| | | /** |
| | | * è·åå½åé¡¹ç®æ è¯ |
| | | */ |
| | | public String getCurrentProjectId() { |
| | | return this.currentProjectId; |
| | | } |
| | | |
| | | /** |
| | | * è·å项ç®å¯¹åºçS7Serializerå®ä¾ |
| | | * 妿ä¸åå¨ï¼åå建ä¸ä¸ªæ°çå®ä¾å¹¶ç¼å |
| | | * |
| | | * @param projectId é¡¹ç®æ è¯ |
| | | * @param config 项ç®é
ç½® |
| | | * @return S7Serializerå®ä¾ |
| | | */ |
| | | private EnhancedS7Serializer getSerializerForProject(String projectId, PlcAddress config) { |
| | | return serializerCache.computeIfAbsent(projectId, id -> { |
| | | // è§£æPLCç±»å |
| | | EPlcType plcType = EPlcType.S1200; // é»è®¤å¼ |
| | | if (config != null && config.getPlcType() != null) { |
| | | try { |
| | | plcType = EPlcType.valueOf(config.getPlcType()); |
| | | } catch (IllegalArgumentException e) { |
| | | log.warn("æªç¥çPLCç±»å: {}, 使ç¨é»è®¤ç±»å S1200", config.getPlcType()); |
| | | } |
| | | } |
| | | |
| | | // å建S7PLCå®ä¾ |
| | | String plcIp = (config != null && config.getPlcIp() != null) ? config.getPlcIp() : "192.168.10.21"; |
| | | S7PLC s7Plc = new S7PLC(plcType, plcIp); |
| | | |
| | | // å建并è¿åEnhancedS7Serializerå®ä¾ |
| | | return EnhancedS7Serializer.newInstance(s7Plc); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * æ¸
餿å®é¡¹ç®çS7Serializerç¼å |
| | | * |
| | | * @param projectId é¡¹ç®æ è¯ |
| | | */ |
| | | public void clearSerializerCache(String projectId) { |
| | | serializerCache.remove(projectId); |
| | | log.info("å·²æ¸
é¤é¡¹ç® {} çS7Serializerç¼å", projectId); |
| | | } |
| | | |
| | | /** |
| | | * æ¸
餿æS7Serializerç¼å |
| | | */ |
| | | public void clearAllSerializerCache() { |
| | | serializerCache.clear(); |
| | | log.info("å·²æ¸
餿æS7Serializerç¼å"); |
| | | } |
| | | // ==================== åºäºDeviceConfigçæ°APIï¼æ¨è使ç¨ï¼ ==================== |
| | | |
| | | /** |
| | | * æ ¹æ®è®¾å¤ID模æPLCåé请æ±å |
| | |
| | | return false; |
| | | } |
| | | try { |
| | | String projectId = resolveProjectId(device); |
| | | PlcAddress config = buildPlcAddressFromDevice(device); |
| | | EnhancedS7Serializer s7Serializer = getSerializerForDevice(device); |
| | | return simulatePlcRequestInternal(projectId, config, s7Serializer); |
| | | if (s7Serializer == null) { |
| | | log.error("è·åS7Serializer失败: deviceId={}", deviceId); |
| | | return false; |
| | | } |
| | | |
| | | // 使ç¨PlcDynamicDataServiceè¯»åæ°æ®ï¼æ¯æaddressMappingï¼ |
| | | Map<String, Object> currentData = plcDynamicDataService.readAllPlcData(device, s7Serializer); |
| | | if (currentData == null || currentData.isEmpty()) { |
| | | log.error("读åPLCæ°æ®å¤±è´¥ï¼è¿å空: deviceId={}", deviceId); |
| | | return false; |
| | | } |
| | | |
| | | // æ£æ¥èæºç¶æ |
| | | Object onlineStateObj = currentData.get("onlineState"); |
| | | Integer onlineState = null; |
| | | if (onlineStateObj != null) { |
| | | if (onlineStateObj instanceof Number) { |
| | | onlineState = ((Number) onlineStateObj).intValue(); |
| | | } else { |
| | | try { |
| | | String strValue = String.valueOf(onlineStateObj); |
| | | if (!strValue.isEmpty() && !"null".equalsIgnoreCase(strValue)) { |
| | | onlineState = Integer.parseInt(strValue); |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | log.warn("è§£æonlineState失败: deviceId={}, value={}", deviceId, onlineStateObj, e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (onlineState != null && onlineState == OFF) { |
| | | log.info("å½åPLCèæºæ¨¡å¼ä¸º0ï¼åæ¢èæº: deviceId={}", deviceId); |
| | | return false; |
| | | } |
| | | |
| | | // æ£æ¥æ±æ¥åï¼å¦æä¸º1åé置为0 |
| | | Object plcReportObj = currentData.get("plcReport"); |
| | | Integer plcReport = null; |
| | | if (plcReportObj != null) { |
| | | if (plcReportObj instanceof Number) { |
| | | plcReport = ((Number) plcReportObj).intValue(); |
| | | } else { |
| | | try { |
| | | String strValue = String.valueOf(plcReportObj); |
| | | if (!strValue.isEmpty() && !"null".equalsIgnoreCase(strValue)) { |
| | | plcReport = Integer.parseInt(strValue); |
| | | } |
| | | } catch (NumberFormatException e) { |
| | | log.warn("è§£æplcReport失败: deviceId={}, value={}", deviceId, plcReportObj, e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (plcReport != null && plcReport == ON) { |
| | | log.info("å½åä¸çPLCæ±æ¥å为1ï¼é置为0: deviceId={}", deviceId); |
| | | currentData.put("plcReport", OFF); |
| | | } |
| | | |
| | | // 设置请æ±å为1 |
| | | currentData.put("plcRequest", ON); |
| | | |
| | | // 使ç¨PlcDynamicDataServiceåå
¥æ°æ® |
| | | plcDynamicDataService.writePlcData(device, currentData, s7Serializer); |
| | | |
| | | log.info("模æPLCåé请æ±åæåï¼plcRequest=1, deviceId={}", deviceId); |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("æ ¹æ®è®¾å¤æ¨¡æPLC请æ±å失败: deviceId={}", deviceId, e); |
| | | return false; |
| | |
| | | return false; |
| | | } |
| | | try { |
| | | String projectId = resolveProjectId(device); |
| | | PlcAddress config = buildPlcAddressFromDevice(device); |
| | | EnhancedS7Serializer s7Serializer = getSerializerForDevice(device); |
| | | return simulatePlcReportInternal(projectId, config, s7Serializer); |
| | | if (s7Serializer == null) { |
| | | log.error("è·åS7Serializer失败: deviceId={}", deviceId); |
| | | return false; |
| | | } |
| | | |
| | | // 使ç¨PlcDynamicDataServiceè¯»åæ°æ® |
| | | Map<String, Object> currentData = plcDynamicDataService.readAllPlcData(device, s7Serializer); |
| | | if (currentData == null || currentData.isEmpty()) { |
| | | log.error("读åPLCæ°æ®å¤±è´¥ï¼è¿å空: deviceId={}", deviceId); |
| | | return false; |
| | | } |
| | | |
| | | // è®¾ç½®æ±æ¥å为1ï¼è¯·æ±åæ¸
0 |
| | | currentData.put("plcReport", ON); |
| | | currentData.put("plcRequest", OFF); |
| | | currentData.put("mesGlassCount", 10); |
| | | |
| | | // 使ç¨PlcDynamicDataServiceåå
¥æ°æ® |
| | | plcDynamicDataService.writePlcData(device, currentData, s7Serializer); |
| | | |
| | | log.info("模æPLCä»»å¡å®ææ±æ¥ï¼plcReport=1, mesGlassCount=10, deviceId={}", deviceId); |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("æ ¹æ®è®¾å¤æ¨¡æPLCæ±æ¥å¤±è´¥: deviceId={}", deviceId, e); |
| | | return false; |
| | |
| | | return false; |
| | | } |
| | | try { |
| | | String projectId = resolveProjectId(device); |
| | | PlcAddress config = buildPlcAddressFromDevice(device); |
| | | EnhancedS7Serializer s7Serializer = getSerializerForDevice(device); |
| | | return resetPlcInternal(projectId, config, s7Serializer); |
| | | if (s7Serializer == null) { |
| | | log.error("è·åS7Serializer失败: deviceId={}", deviceId); |
| | | return false; |
| | | } |
| | | |
| | | // æå»ºéç½®æ°æ® |
| | | Map<String, Object> resetData = new HashMap<>(); |
| | | resetData.put("plcRequest", OFF); |
| | | resetData.put("plcReport", OFF); |
| | | resetData.put("mesSend", OFF); |
| | | resetData.put("mesConfirm", OFF); |
| | | resetData.put("onlineState", ON); |
| | | resetData.put("mesGlassCount", 0); |
| | | resetData.put("alarmInfo", OFF); |
| | | |
| | | // 使ç¨PlcDynamicDataServiceåå
¥æ°æ® |
| | | plcDynamicDataService.writePlcData(device, resetData, s7Serializer); |
| | | |
| | | log.info("PLCç¶æå·²éç½®, deviceId={}", deviceId); |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("æ ¹æ®è®¾å¤éç½®PLCç¶æå¤±è´¥: deviceId={}", deviceId, e); |
| | | return false; |
| | |
| | | return null; |
| | | } |
| | | try { |
| | | String projectId = resolveProjectId(device); |
| | | PlcAddress config = buildPlcAddressFromDevice(device); |
| | | EnhancedS7Serializer s7Serializer = getSerializerForDevice(device); |
| | | PlcBaseData data = readPlcStatusInternal(projectId, config, s7Serializer); |
| | | if (data == null) { |
| | | if (s7Serializer == null) { |
| | | log.error("è·åS7Serializer失败: deviceId={}", deviceId); |
| | | return null; |
| | | } |
| | | String json = objectMapper.writeValueAsString(data); |
| | | return objectMapper.readValue(json, MAP_TYPE); |
| | | // 使ç¨PlcDynamicDataServiceè¯»åæææ°æ®ï¼æ¯æaddressMappingï¼ |
| | | Map<String, Object> data = plcDynamicDataService.readAllPlcData(device, s7Serializer); |
| | | return data; |
| | | } catch (Exception e) { |
| | | log.error("读å设å¤PLCç¶æå¤±è´¥: deviceId={}", deviceId, e); |
| | | return null; |
| | |
| | | } |
| | | |
| | | try { |
| | | // ä»è®¾å¤é
ç½®ä¸è·åé¡¹ç®æ è¯ |
| | | String projectId = resolveProjectId(device); |
| | | |
| | | // è·å对åºçS7Serializerï¼ä½¿ç¨è®¾å¤é
ç½®ï¼ |
| | | EnhancedS7Serializer s7Serializer = getSerializerForDevice(device); |
| | | if (s7Serializer == null) { |
| | | log.error("è·åS7Serializer失败: deviceId={}", deviceId); |
| | | return false; |
| | | } |
| | | |
| | | // 使ç¨å¨ææ°æ®æå¡åå
¥å段ï¼åºäºDeviceConfigï¼ |
| | | plcDynamicDataService.writePlcData(device, fieldValues, s7Serializer); |
| | | |
| | | log.info("åå
¥PLCåæ®µæå: deviceId={}, projectId={}, fields={}", deviceId, projectId, fieldValues.keySet()); |
| | | log.info("åå
¥PLCåæ®µæå: deviceId={}, fields={}", deviceId, fieldValues.keySet()); |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("åå
¥PLCåæ®µå¤±è´¥: deviceId={}", deviceId, e); |
| | |
| | | * @return S7Serializerå®ä¾ |
| | | */ |
| | | private EnhancedS7Serializer getSerializerForDevice(DeviceConfig device) { |
| | | String cacheKey = "device:" + (device.getId() != null ? device.getId() : resolveProjectId(device)); |
| | | return serializerCache.computeIfAbsent(cacheKey, id -> { |
| | | // è§£æPLCç±»åï¼ä»
åå®ä½åæ®µï¼ |
| | | EPlcType plcType = EPlcType.S1200; |
| | | String plcTypeValue = device.getPlcType(); |
| | | if (plcTypeValue == null || plcTypeValue.isEmpty()) { |
| | | log.warn("è®¾å¤æªé
ç½®PLCç±»åï¼ä½¿ç¨é»è®¤ç±»åS1200, deviceId={}", device.getId()); |
| | | if (device == null) { |
| | | log.error("设å¤é
ç½®ä¸ºç©ºï¼æ æ³å建S7Serializer"); |
| | | return null; |
| | | } |
| | | |
| | | try { |
| | | String cacheKey; |
| | | if (device.getId() != null) { |
| | | cacheKey = "device:" + device.getId(); |
| | | } else { |
| | | try { |
| | | plcType = EPlcType.valueOf(plcTypeValue); |
| | | } catch (IllegalArgumentException e) { |
| | | log.warn("æªç¥çPLCç±»å: {}, 使ç¨é»è®¤ç±»å S1200", plcTypeValue); |
| | | cacheKey = "device:" + resolveProjectId(device); |
| | | } catch (Exception e) { |
| | | cacheKey = "device:" + (device.getDeviceCode() != null ? device.getDeviceCode() : "unknown"); |
| | | } |
| | | } |
| | | |
| | | // å建S7PLCå®ä¾ï¼ä»
åå®ä½åæ®µï¼ |
| | | String plcIp = device.getPlcIp(); |
| | | if (plcIp == null || plcIp.isEmpty()) { |
| | | log.warn("è®¾å¤æªé
ç½®PLC IPï¼ä½¿ç¨é»è®¤ 192.168.10.21, deviceId={}", device.getId()); |
| | | plcIp = "192.168.10.21"; |
| | | } |
| | | S7PLC s7Plc = new S7PLC(plcType, plcIp); |
| | | |
| | | // å建并è¿åEnhancedS7Serializerå®ä¾ |
| | | return EnhancedS7Serializer.newInstance(s7Plc); |
| | | }); |
| | | return serializerCache.computeIfAbsent(cacheKey, id -> { |
| | | try { |
| | | // è§£æPLCç±»åï¼ä»
åå®ä½åæ®µï¼ |
| | | EPlcType plcType = EPlcType.S1200; |
| | | String plcTypeValue = device.getPlcType(); |
| | | if (plcTypeValue == null || plcTypeValue.isEmpty()) { |
| | | log.warn("è®¾å¤æªé
ç½®PLCç±»åï¼ä½¿ç¨é»è®¤ç±»åS1200, deviceId={}", device.getId()); |
| | | } else { |
| | | try { |
| | | plcType = EPlcType.valueOf(plcTypeValue); |
| | | } catch (IllegalArgumentException e) { |
| | | log.warn("æªç¥çPLCç±»å: {}, 使ç¨é»è®¤ç±»å S1200", plcTypeValue); |
| | | } |
| | | } |
| | | |
| | | // å建S7PLCå®ä¾ï¼ä»
åå®ä½åæ®µï¼ |
| | | String plcIp = device.getPlcIp(); |
| | | if (plcIp == null || plcIp.isEmpty()) { |
| | | log.warn("è®¾å¤æªé
ç½®PLC IPï¼ä½¿ç¨é»è®¤ 192.168.10.21, deviceId={}", device.getId()); |
| | | plcIp = "192.168.10.21"; |
| | | } |
| | | S7PLC s7Plc = new S7PLC(plcType, plcIp); |
| | | |
| | | // å建并è¿åEnhancedS7Serializerå®ä¾ |
| | | EnhancedS7Serializer serializer = EnhancedS7Serializer.newInstance(s7Plc); |
| | | if (serializer == null) { |
| | | log.error("å建EnhancedS7Serializer失败: deviceId={}, plcIp={}, plcType={}", |
| | | device.getId(), plcIp, plcType); |
| | | } |
| | | return serializer; |
| | | } catch (Exception e) { |
| | | log.error("å建S7Serializerå¼å¸¸: deviceId={}", device.getId(), e); |
| | | return null; |
| | | } |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error("è·åS7Serializer失败: deviceId={}", device.getId(), e); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private PlcAddress buildPlcAddressFromDevice(DeviceConfig device) { |
| | | Map<String, Object> plcConfig = getPlcConfigParams(device); |
| | | String dbArea = plcConfig.get("dbArea") != null ? String.valueOf(plcConfig.get("dbArea")) : "DB12"; |
| | | int beginIndex = plcConfig.get("beginIndex") != null ? parseInteger(plcConfig.get("beginIndex")) : 0; |
| | | |
| | | String plcIp = device.getPlcIp(); |
| | | if (plcIp == null || plcIp.isEmpty()) { |
| | | log.warn("è®¾å¤æªé
ç½®PLC IPï¼ä½¿ç¨é»è®¤ 192.168.10.21, deviceId={}", device.getId()); |
| | | plcIp = "192.168.10.21"; |
| | | } |
| | | |
| | | String plcType = device.getPlcType(); |
| | | if (plcType == null || plcType.isEmpty()) { |
| | | log.warn("è®¾å¤æªé
ç½®PLCç±»åï¼ä½¿ç¨é»è®¤S1200, deviceId={}", device.getId()); |
| | | plcType = EPlcType.S1200.name(); |
| | | } |
| | | |
| | | String addressMapping = resolveAddressMapping(device); |
| | | |
| | | PlcAddress config = new PlcAddress(); |
| | | config.setProjectId(resolveProjectId(device)); |
| | | config.setDbArea(dbArea); |
| | | config.setBeginIndex(beginIndex); |
| | | config.setPlcIp(plcIp); |
| | | config.setPlcType(plcType); |
| | | config.setAddressMapping(addressMapping); |
| | | return config; |
| | | } |
| | | |
| | | private String resolveAddressMapping(DeviceConfig device) { |
| | | Map<String, Object> mapping = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper); |
| | | if (!mapping.isEmpty()) { |
| | | try { |
| | | return objectMapper.writeValueAsString(mapping); |
| | | } catch (Exception e) { |
| | | log.warn("åºååconfigJsonåæ®µæ å°å¤±è´¥, deviceId={}", device.getId(), e); |
| | | } |
| | | } |
| | | Map<String, Object> extraParams = parseExtraParams(device); |
| | | Object addressMapping = extraParams.get("addressMapping"); |
| | | if (addressMapping instanceof String) { |
| | | return (String) addressMapping; |
| | | } |
| | | if (addressMapping != null) { |
| | | try { |
| | | return objectMapper.writeValueAsString(addressMapping); |
| | | } catch (Exception e) { |
| | | log.warn("åºååextraParams.addressMapping失败, deviceId={}", device.getId(), e); |
| | | } |
| | | } |
| | | throw new IllegalStateException("è®¾å¤æªé
ç½®PLCåæ®µæ å°, deviceId=" + device.getId()); |
| | | } |
| | | |
| | | private Map<String, Object> parseExtraParams(DeviceConfig device) { |
| | | if (device.getExtraParams() == null || device.getExtraParams().trim().isEmpty()) { |
| | |
| | | |
| | | throw new IllegalStateException("æ æ³è§£æè®¾å¤çPLCé¡¹ç®æ è¯, deviceId=" + device.getId()); |
| | | } |
| | | } |
| | | } |
| | |
| | | import com.github.xingshuangs.iot.protocol.s7.serializer.S7Parameter; |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.device.util.ConfigJsonHelper; |
| | | import com.mes.entity.PlcAddress; |
| | | import com.mes.s7.enhanced.EnhancedS7Serializer; |
| | | import com.mes.service.PlcDynamicDataService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import com.github.xingshuangs.iot.protocol.s7.serializer.S7Variable; |
| | | |
| | | import java.lang.reflect.Field; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | |
| | | |
| | | /** |
| | | * PLCå¨ææ°æ®è¯»åæå¡å®ç° |
| | | * éè¿PlcAddressä¸çaddressMappingé
ç½®å¨æè¯»åä»»æå段ç»å |
| | | * éè¿DeviceConfigä¸çconfigJsoné
ç½®å¨æè¯»åä»»æå段ç»å |
| | | * |
| | | * @author huang |
| | | * @date 2025/11/05 |
| | |
| | | private final ObjectMapper objectMapper = new ObjectMapper(); |
| | | private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {}; |
| | | |
| | | /** |
| | | * æ ¹æ®PlcAddressé
ç½®ååæ®µå称读åPLCæ°æ® |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param fieldNames è¦è¯»åçåæ®µåç§°å表 |
| | | * @param s7Serializer S7åºååå¨ |
| | | * @return åæ®µå->å¼ çMap |
| | | */ |
| | | @Override |
| | | public Map<String, Object> readPlcData(PlcAddress config, List<String> fieldNames, EnhancedS7Serializer s7Serializer) { |
| | | if (config == null || config.getAddressMapping() == null) { |
| | | throw new IllegalArgumentException("PlcAddressé
ç½®æaddressMappingä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | try { |
| | | // è§£æaddressMapping JSONé
ç½® |
| | | JSONObject addressMapping = JSONObject.parseObject(config.getAddressMapping()); |
| | | |
| | | // æå»ºS7Parameterå表 |
| | | List<S7Parameter> parameters = buildS7Parameters(config, addressMapping, fieldNames); |
| | | |
| | | // ä»PLCè¯»åæ°æ® |
| | | List<S7Parameter> results = s7Serializer.read(parameters); |
| | | |
| | | // å°ç»æè½¬æ¢ä¸ºMap |
| | | Map<String, Object> resultMap = new HashMap<>(); |
| | | for (int i = 0; i < fieldNames.size() && i < results.size(); i++) { |
| | | String fieldName = fieldNames.get(i); |
| | | Object value = results.get(i).getValue(); |
| | | resultMap.put(fieldName, value); |
| | | } |
| | | |
| | | return resultMap; |
| | | } catch (Exception e) { |
| | | log.error("读åPLCæ°æ®å¤±è´¥ï¼è¯·æ£æ¥ï¼1.PLC IPå°åæ¯å¦æ£ç¡®[{}] 2.PLCè®¾å¤æ¯å¦å¨çº¿ 3.ç½ç»è¿æ¥æ¯å¦æ£å¸¸ï¼è¯¦ç»é误: {}", |
| | | config.getPlcIp(), e.getMessage(), e); |
| | | return new HashMap<>(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®PlcAddressé
ç½®åæ°æ®Mapåå
¥PLC |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param dataMap åæ®µå->å¼ çMap |
| | | * @param s7Serializer S7åºååå¨ |
| | | */ |
| | | @Override |
| | | public void writePlcData(PlcAddress config, Map<String, Object> dataMap, EnhancedS7Serializer s7Serializer) { |
| | | if (config == null || config.getAddressMapping() == null) { |
| | | throw new IllegalArgumentException("PlcAddressé
ç½®æaddressMappingä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | try { |
| | | // è§£æaddressMapping JSONé
ç½® |
| | | JSONObject addressMapping = JSONObject.parseObject(config.getAddressMapping()); |
| | | |
| | | // æå»ºS7Parameterå表ï¼å¹¶å¡«å
å¼ |
| | | List<S7Parameter> parameters = buildS7ParametersWithValues(config, addressMapping, dataMap); |
| | | |
| | | // åå
¥PLC |
| | | s7Serializer.write(parameters); |
| | | } catch (Exception e) { |
| | | log.error("åå
¥PLCæ°æ®å¤±è´¥ï¼è¯·æ£æ¥ï¼1.PLC IPå°åæ¯å¦æ£ç¡®[{}] 2.PLCè®¾å¤æ¯å¦å¨çº¿ 3.ç½ç»è¿æ¥æ¯å¦æ£å¸¸ï¼è¯¦ç»é误: {}", |
| | | config.getPlcIp(), e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读åPLCææå段 |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param s7Serializer S7åºååå¨ |
| | | * @return ææå段çå¼ |
| | | */ |
| | | @Override |
| | | public Map<String, Object> readAllPlcData(PlcAddress config, EnhancedS7Serializer s7Serializer) { |
| | | if (config == null || config.getAddressMapping() == null) { |
| | | throw new IllegalArgumentException("PlcAddressé
ç½®æaddressMappingä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | // è·åææå段å |
| | | JSONObject addressMapping = JSONObject.parseObject(config.getAddressMapping()); |
| | | List<String> allFields = new ArrayList<>(addressMapping.keySet()); |
| | | |
| | | // è¯»åææåæ®µ |
| | | return readPlcData(config, allFields, s7Serializer); |
| | | } |
| | | |
| | | /** |
| | | * 读ååä¸ªåæ®µ |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param fieldName åæ®µå |
| | | * @param s7Serializer S7åºååå¨ |
| | | * @return åæ®µå¼ |
| | | */ |
| | | @Override |
| | | public Object readPlcField(PlcAddress config, String fieldName, EnhancedS7Serializer s7Serializer) { |
| | | List<String> fields = new ArrayList<>(); |
| | | fields.add(fieldName); |
| | | |
| | | Map<String, Object> result = readPlcData(config, fields, s7Serializer); |
| | | return result.get(fieldName); |
| | | } |
| | | |
| | | /** |
| | | * åå
¥åä¸ªåæ®µ |
| | | * |
| | | * @param config PLCå°åæ å°é
ç½® |
| | | * @param fieldName åæ®µå |
| | | * @param value åæ®µå¼ |
| | | * @param s7Serializer S7åºååå¨ |
| | | */ |
| | | @Override |
| | | public void writePlcField(PlcAddress config, String fieldName, Object value, EnhancedS7Serializer s7Serializer) { |
| | | Map<String, Object> dataMap = new HashMap<>(); |
| | | dataMap.put(fieldName, value); |
| | | |
| | | writePlcData(config, dataMap, s7Serializer); |
| | | } |
| | | |
| | | /** |
| | | * æå»ºS7Parameterå表ï¼ä¸å
å«å¼ï¼ |
| | | * |
| | | * @param config PLCå°åé
ç½® |
| | | * @param addressMapping å°åæ å° |
| | | * @param fieldNames åæ®µåå表 |
| | | * @return S7Parameterå表 |
| | | */ |
| | | private List<S7Parameter> buildS7Parameters(PlcAddress config, JSONObject addressMapping, List<String> fieldNames) { |
| | | List<S7Parameter> parameters = new ArrayList<>(); |
| | | |
| | | for (String fieldName : fieldNames) { |
| | | if (!addressMapping.containsKey(fieldName)) { |
| | | log.warn("åæ®µ {} å¨addressMappingä¸ä¸åå¨ï¼è·³è¿", fieldName); |
| | | continue; |
| | | } |
| | | |
| | | // è·ååæ®µçåç§»å°å |
| | | int offset = addressMapping.getInteger(fieldName); |
| | | |
| | | // æå»ºå®æ´å°åï¼dbArea + offsetï¼å¦ï¼DB12.2ï¼ |
| | | String fullAddress = config.getDbArea() + "." + offset; |
| | | |
| | | // å建S7Parameterï¼é»è®¤ä½¿ç¨UINT16ç±»åï¼16使 ç¬¦å·æ´æ°ï¼ |
| | | S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1); |
| | | parameters.add(parameter); |
| | | } |
| | | |
| | | return parameters; |
| | | } |
| | | |
| | | /** |
| | | * æå»ºS7Parameterå表ï¼å
å«å¼ï¼ |
| | | * |
| | | * @param config PLCå°åé
ç½® |
| | | * @param addressMapping å°åæ å° |
| | | * @param dataMap åæ®µå->å¼ çMap |
| | | * @return S7Parameterå表 |
| | | */ |
| | | private List<S7Parameter> buildS7ParametersWithValues(PlcAddress config, JSONObject addressMapping, Map<String, Object> dataMap) { |
| | | List<S7Parameter> parameters = new ArrayList<>(); |
| | | |
| | | for (Map.Entry<String, Object> entry : dataMap.entrySet()) { |
| | | String fieldName = entry.getKey(); |
| | | Object value = entry.getValue(); |
| | | |
| | | if (!addressMapping.containsKey(fieldName)) { |
| | | log.warn("åæ®µ {} å¨addressMappingä¸ä¸åå¨ï¼è·³è¿", fieldName); |
| | | continue; |
| | | } |
| | | |
| | | // è·ååæ®µçåç§»å°å |
| | | int offset = addressMapping.getInteger(fieldName); |
| | | |
| | | // æå»ºå®æ´å°å |
| | | String fullAddress = config.getDbArea() + "." + offset; |
| | | |
| | | // å建S7Parameterï¼è®¾ç½®å¼ |
| | | S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1); |
| | | parameter.setValue(value); |
| | | parameters.add(parameter); |
| | | } |
| | | |
| | | return parameters; |
| | | } |
| | | |
| | | /** |
| | | * ä»DeviceConfig䏿åå°åæ å°é
ç½® |
| | | * |
| | |
| | | throw new IllegalArgumentException("设å¤é
ç½®ä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | String addressMapping = extractAddressMapping(device); |
| | | if (addressMapping == null || addressMapping.isEmpty()) { |
| | | throw new IllegalArgumentException("设å¤é
ç½®ä¸addressMappingä¸è½ä¸ºç©º"); |
| | | if (s7Serializer == null) { |
| | | log.error("S7Serializerä¸ºç©ºï¼æ æ³åå
¥PLCæ°æ®: deviceId={}", device.getId()); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | String addressMapping = extractAddressMapping(device); |
| | | if (addressMapping == null || addressMapping.isEmpty()) { |
| | | log.error("设å¤é
ç½®ä¸addressMapping为空: deviceId={}", device.getId()); |
| | | return; |
| | | } |
| | | |
| | | // è§£æaddressMapping JSONé
ç½® |
| | | JSONObject addressMappingObj = JSONObject.parseObject(addressMapping); |
| | | |
| | |
| | | throw new IllegalArgumentException("设å¤é
ç½®ä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | String addressMapping = extractAddressMapping(device); |
| | | if (addressMapping == null || addressMapping.isEmpty()) { |
| | | throw new IllegalArgumentException("设å¤é
ç½®ä¸addressMappingä¸è½ä¸ºç©º"); |
| | | if (s7Serializer == null) { |
| | | log.error("S7Serializerä¸ºç©ºï¼æ æ³è¯»åPLCæ°æ®: deviceId={}", device.getId()); |
| | | return new HashMap<>(); |
| | | } |
| | | |
| | | // è·åææå段å |
| | | JSONObject addressMappingObj = JSONObject.parseObject(addressMapping); |
| | | List<String> allFields = new ArrayList<>(addressMappingObj.keySet()); |
| | | |
| | | // è¯»åææåæ®µ |
| | | return readPlcData(device, allFields, s7Serializer); |
| | | try { |
| | | String addressMapping = extractAddressMapping(device); |
| | | if (addressMapping == null || addressMapping.isEmpty()) { |
| | | log.error("设å¤é
ç½®ä¸addressMapping为空: deviceId={}", device.getId()); |
| | | return new HashMap<>(); |
| | | } |
| | | |
| | | // è·åææå段å |
| | | JSONObject addressMappingObj = JSONObject.parseObject(addressMapping); |
| | | List<String> allFields = new ArrayList<>(addressMappingObj.keySet()); |
| | | |
| | | // è¯»åææåæ®µ |
| | | return readPlcData(device, allFields, s7Serializer); |
| | | } catch (Exception e) { |
| | | log.error("è¯»åææPLCæ°æ®å¤±è´¥: deviceId={}", device.getId(), e); |
| | | return new HashMap<>(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | writePlcData(device, dataMap, s7Serializer); |
| | | } |
| | | |
| | | @Override |
| | | public <T> void writePlcDataByEntity(DeviceConfig device, T entity, EnhancedS7Serializer s7Serializer) { |
| | | if (device == null || entity == null) { |
| | | throw new IllegalArgumentException("设å¤é
ç½®åå®ä½å¯¹è±¡ä¸è½ä¸ºç©º"); |
| | | } |
| | | |
| | | try { |
| | | // 1. ä»configJsonä¸è·åå°åæ å°ï¼å段å -> åç§»éï¼ |
| | | Map<String, Object> addressMapping = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper); |
| | | if (addressMapping.isEmpty()) { |
| | | throw new IllegalArgumentException("设å¤é
ç½®ä¸æªæ¾å°å°åæ å°é
ç½®, deviceId=" + device.getId()); |
| | | } |
| | | |
| | | // 2. è·ådbAreaåbeginIndex |
| | | String dbArea = extractDbArea(device); |
| | | int beginIndex = extractBeginIndex(device); |
| | | |
| | | // 3. è·ååæ®µé
ç½®ï¼ç±»ååcountï¼ |
| | | Map<String, FieldConfig> fieldConfigMap = extractFieldConfigMap(device); |
| | | |
| | | // 4. è§£æå®ä½ç±»ï¼è·åææå¸¦@S7Variable注解çåæ®µ |
| | | Class<?> entityClass = entity.getClass(); |
| | | List<S7Parameter> parameters = new ArrayList<>(); |
| | | |
| | | for (Field field : entityClass.getDeclaredFields()) { |
| | | S7Variable annotation = field.getAnnotation(S7Variable.class); |
| | | if (annotation == null) { |
| | | continue; |
| | | } |
| | | |
| | | // è·ååæ®µåï¼ä»æ³¨è§£çaddressè·åï¼å¯¹åºconfigJsonä¸çparamKeyï¼ |
| | | String fieldName = annotation.address(); |
| | | if (fieldName == null || fieldName.isEmpty()) { |
| | | continue; |
| | | } |
| | | |
| | | // ä»addressMappingä¸è·ååç§»é |
| | | Object offsetObj = addressMapping.get(fieldName); |
| | | if (offsetObj == null) { |
| | | log.warn("åæ®µ {} å¨configJsonå°åæ å°ä¸ä¸åå¨ï¼è·³è¿", fieldName); |
| | | continue; |
| | | } |
| | | |
| | | int offset; |
| | | if (offsetObj instanceof Number) { |
| | | offset = ((Number) offsetObj).intValue(); |
| | | } else { |
| | | offset = Integer.parseInt(String.valueOf(offsetObj)); |
| | | } |
| | | |
| | | // æå»ºå®æ´å°åï¼dbArea + (beginIndex + offset) |
| | | String fullAddress = dbArea + "." + (beginIndex + offset); |
| | | |
| | | // ç¡®å®æ°æ®ç±»ååcount |
| | | // ä¼å
级ï¼1. 注解ä¸çtypeåcount 2. configJsonä¸çé
ç½® 3. æ ¹æ®åæ®µåæ¨æ |
| | | EDataType dataType = annotation.type(); |
| | | int count = annotation.count(); |
| | | |
| | | // å¦ææ³¨è§£ä¸æ²¡ææå®countï¼å°è¯ä»configJsonæåæ®µåæ¨æ |
| | | if (count <= 0) { |
| | | FieldConfig fieldConfig = fieldConfigMap.get(fieldName); |
| | | if (fieldConfig != null && fieldConfig.count > 0) { |
| | | count = fieldConfig.count; |
| | | } else { |
| | | count = determineFieldCountByName(fieldName); |
| | | } |
| | | } |
| | | |
| | | // å¦ææ³¨è§£ä¸çç±»åæ¯UINT16ä½å段æ¯Stringç±»åï¼å°è¯ä»configJsonè·å |
| | | if (dataType == EDataType.UINT16 && field.getType() == String.class) { |
| | | FieldConfig fieldConfig = fieldConfigMap.get(fieldName); |
| | | if (fieldConfig != null && fieldConfig.dataType != null) { |
| | | dataType = fieldConfig.dataType; |
| | | } else { |
| | | dataType = determineFieldTypeByName(fieldName); |
| | | } |
| | | } |
| | | |
| | | // è·ååæ®µå¼ |
| | | field.setAccessible(true); |
| | | Object value = field.get(entity); |
| | | if (value == null) { |
| | | continue; // è·³è¿nullå¼ |
| | | } |
| | | |
| | | // å建S7Parameterå¹¶è®¾ç½®å¼ |
| | | S7Parameter parameter = new S7Parameter(fullAddress, dataType, count); |
| | | parameter.setValue(value); |
| | | parameters.add(parameter); |
| | | } |
| | | |
| | | if (parameters.isEmpty()) { |
| | | log.warn("å®ä½ç±» {} 䏿²¡ææ¾å°ææçåæ®µï¼æ æ³åå
¥PLC", entityClass.getSimpleName()); |
| | | return; |
| | | } |
| | | |
| | | // 5. åå
¥PLC |
| | | s7Serializer.write(parameters); |
| | | |
| | | log.info("æ ¹æ®å®ä½ç±»åå
¥PLCæ°æ®æå: deviceId={}, entityClass={}, fields={}", |
| | | device.getId(), entityClass.getSimpleName(), parameters.size()); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("æ ¹æ®å®ä½ç±»åå
¥PLCæ°æ®å¤±è´¥: deviceId={}, entityClass={}", |
| | | device.getId(), entity != null ? entity.getClass().getSimpleName() : "null", e); |
| | | throw new RuntimeException("åå
¥PLCæ°æ®å¤±è´¥: " + e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * æå»ºS7Parameterå表ï¼ä¸å
å«å¼ï¼- åºäºDeviceConfig |
| | | */ |
| | | private List<S7Parameter> buildS7ParametersForDevice(DeviceConfig device, String dbArea, JSONObject addressMapping, List<String> fieldNames) { |
| | | List<S7Parameter> parameters = new ArrayList<>(); |
| | | |
| | | // è·ååæ®µé
ç½®ï¼ä»configJsonä¸è§£æç±»ååcountï¼ |
| | | Map<String, FieldConfig> fieldConfigMap = extractFieldConfigMap(device); |
| | | |
| | | for (String fieldName : fieldNames) { |
| | | if (!addressMapping.containsKey(fieldName)) { |
| | |
| | | continue; |
| | | } |
| | | |
| | | // è·ååæ®µçåç§»å°å |
| | | int offset = addressMapping.getInteger(fieldName); |
| | | // è·ååæ®µçåç§»å°åï¼addressMappingä¸åªå卿°ååç§»éï¼ |
| | | Object offsetObj = addressMapping.get(fieldName); |
| | | int offset; |
| | | if (offsetObj instanceof Number) { |
| | | offset = ((Number) offsetObj).intValue(); |
| | | } else { |
| | | offset = Integer.parseInt(String.valueOf(offsetObj)); |
| | | } |
| | | |
| | | // æå»ºå®æ´å°åï¼dbArea + offsetï¼å¦ï¼DB12.2ï¼ |
| | | String fullAddress = dbArea + "." + offset; |
| | | |
| | | // å建S7Parameterï¼é»è®¤ä½¿ç¨UINT16ç±»åï¼16使 ç¬¦å·æ´æ°ï¼ |
| | | S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1); |
| | | // è·ååæ®µç±»ååé¿åº¦ï¼ä»configJsonææ ¹æ®åæ®µåæ¨æï¼ |
| | | FieldConfig fieldConfig = fieldConfigMap.getOrDefault(fieldName, new FieldConfig()); |
| | | EDataType dataType = fieldConfig.dataType != null ? fieldConfig.dataType : determineFieldTypeByName(fieldName); |
| | | int count = fieldConfig.count > 0 ? fieldConfig.count : determineFieldCountByName(fieldName); |
| | | |
| | | // å建S7Parameter |
| | | S7Parameter parameter = new S7Parameter(fullAddress, dataType, count); |
| | | parameters.add(parameter); |
| | | } |
| | | |
| | |
| | | private List<S7Parameter> buildS7ParametersWithValuesForDevice(DeviceConfig device, String dbArea, JSONObject addressMapping, Map<String, Object> dataMap) { |
| | | List<S7Parameter> parameters = new ArrayList<>(); |
| | | |
| | | // è·ååæ®µé
ç½®ï¼ä»configJsonä¸è§£æç±»ååcountï¼ |
| | | Map<String, FieldConfig> fieldConfigMap = extractFieldConfigMap(device); |
| | | |
| | | for (Map.Entry<String, Object> entry : dataMap.entrySet()) { |
| | | String fieldName = entry.getKey(); |
| | | Object value = entry.getValue(); |
| | |
| | | continue; |
| | | } |
| | | |
| | | // è·ååæ®µçåç§»å°å |
| | | int offset = addressMapping.getInteger(fieldName); |
| | | // è·ååæ®µçåç§»å°åï¼addressMappingä¸åªå卿°ååç§»éï¼ |
| | | Object offsetObj = addressMapping.get(fieldName); |
| | | int offset; |
| | | if (offsetObj instanceof Number) { |
| | | offset = ((Number) offsetObj).intValue(); |
| | | } else { |
| | | offset = Integer.parseInt(String.valueOf(offsetObj)); |
| | | } |
| | | |
| | | // æå»ºå®æ´å°å |
| | | String fullAddress = dbArea + "." + offset; |
| | | |
| | | // è·ååæ®µç±»ååé¿åº¦ï¼ä»configJsonææ ¹æ®åæ®µåæ¨æï¼ |
| | | FieldConfig fieldConfig = fieldConfigMap.getOrDefault(fieldName, new FieldConfig()); |
| | | EDataType dataType = fieldConfig.dataType != null ? fieldConfig.dataType : determineFieldTypeByName(fieldName); |
| | | int count = fieldConfig.count > 0 ? fieldConfig.count : determineFieldCountByName(fieldName); |
| | | |
| | | // å建S7Parameterï¼è®¾ç½®å¼ |
| | | S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1); |
| | | S7Parameter parameter = new S7Parameter(fullAddress, dataType, count); |
| | | parameter.setValue(value); |
| | | parameters.add(parameter); |
| | | } |
| | | |
| | | return parameters; |
| | | } |
| | | |
| | | /** |
| | | * åæ®µé
ç½®ä¿¡æ¯ |
| | | */ |
| | | private static class FieldConfig { |
| | | EDataType dataType; |
| | | int count; |
| | | |
| | | FieldConfig() { |
| | | this.dataType = null; |
| | | this.count = 0; |
| | | } |
| | | |
| | | FieldConfig(EDataType dataType, int count) { |
| | | this.dataType = dataType; |
| | | this.count = count; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä»è®¾å¤é
ç½®ä¸æååæ®µé
ç½®æ å°ï¼ç±»ååcountï¼ |
| | | * configJsonæ ¼å¼: [{paramKey: "plcGlassId1", paramValue: "4", description: "ç»çid1", dataType: "STRING", count: 20}] |
| | | */ |
| | | private Map<String, FieldConfig> extractFieldConfigMap(DeviceConfig device) { |
| | | Map<String, FieldConfig> configMap = new HashMap<>(); |
| | | |
| | | try { |
| | | String configJson = device.getConfigJson(); |
| | | if (configJson == null || configJson.trim().isEmpty()) { |
| | | return configMap; |
| | | } |
| | | |
| | | String trimmed = configJson.trim(); |
| | | // 妿configJsonæ¯æ°ç»æ ¼å¼ï¼è§£ææ°ç» |
| | | if (trimmed.startsWith("[")) { |
| | | List<Map<String, Object>> paramList = objectMapper.readValue(trimmed, |
| | | new TypeReference<List<Map<String, Object>>>() {}); |
| | | |
| | | for (Map<String, Object> param : paramList) { |
| | | Object paramKeyObj = param.get("paramKey"); |
| | | if (paramKeyObj == null) { |
| | | continue; |
| | | } |
| | | String paramKey = String.valueOf(paramKeyObj); |
| | | |
| | | EDataType dataType = null; |
| | | int count = 0; |
| | | |
| | | // è§£ædataType |
| | | Object dataTypeObj = param.get("dataType"); |
| | | if (dataTypeObj != null) { |
| | | String dataTypeStr = String.valueOf(dataTypeObj).toUpperCase(); |
| | | try { |
| | | dataType = EDataType.valueOf(dataTypeStr); |
| | | } catch (IllegalArgumentException e) { |
| | | log.debug("æ æ³è§£ææ°æ®ç±»å: {}, åæ®µ: {}", dataTypeStr, paramKey); |
| | | } |
| | | } |
| | | |
| | | // è§£æcount |
| | | Object countObj = param.get("count"); |
| | | if (countObj != null) { |
| | | if (countObj instanceof Number) { |
| | | count = ((Number) countObj).intValue(); |
| | | } else { |
| | | try { |
| | | count = Integer.parseInt(String.valueOf(countObj)); |
| | | } catch (NumberFormatException e) { |
| | | log.debug("æ æ³è§£æcountå¼: {}, åæ®µ: {}", countObj, paramKey); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (dataType != null || count > 0) { |
| | | configMap.put(paramKey, new FieldConfig(dataType, count)); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.debug("è§£æå段é
ç½®æ å°å¤±è´¥: {}", e.getMessage()); |
| | | } |
| | | |
| | | return configMap; |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®åæ®µåæ¨ææ°æ®ç±»å |
| | | */ |
| | | private EDataType determineFieldTypeByName(String fieldName) { |
| | | if (fieldName == null) { |
| | | return EDataType.UINT16; |
| | | } |
| | | |
| | | String lowerName = fieldName.toLowerCase(); |
| | | // ç»çIDåæ®µé常æ¯å符串 |
| | | if (lowerName.contains("glassid") || lowerName.contains("glass_id") || |
| | | lowerName.startsWith("plcglassid")) { |
| | | return EDataType.STRING; |
| | | } |
| | | |
| | | // é»è®¤è¿åUINT16 |
| | | return EDataType.UINT16; |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®åæ®µåæ¨æå段é¿åº¦/æ°é |
| | | */ |
| | | private int determineFieldCountByName(String fieldName) { |
| | | if (fieldName == null) { |
| | | return 1; |
| | | } |
| | | |
| | | String lowerName = fieldName.toLowerCase(); |
| | | // ç»çIDé常æ¯20个å符 |
| | | if (lowerName.contains("glassid") || lowerName.contains("glass_id") || |
| | | lowerName.startsWith("plcglassid")) { |
| | | return 20; // é»è®¤20个å符 |
| | | } |
| | | |
| | | // é»è®¤è¿å1 |
| | | return 1; |
| | | } |
| | | } |
| New file |
| | |
| | | package com.mes.task.controller; |
| | | |
| | | import com.mes.task.service.TaskStatusNotificationService; |
| | | import com.mes.vo.Result; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
| | | |
| | | /** |
| | | * ä»»å¡ç¶æéç¥æ§å¶å¨ |
| | | * æä¾SSE端ç¹ç¨äºå®æ¶æ¨é任塿§è¡ç¶æ |
| | | * |
| | | * @author mes |
| | | * @since 2025-01-XX |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/api/plcSend/task/notification") |
| | | @Api(tags = "ä»»å¡ç¶æéç¥") |
| | | @RequiredArgsConstructor |
| | | public class TaskStatusNotificationController { |
| | | |
| | | private final TaskStatusNotificationService notificationService; |
| | | |
| | | @GetMapping(value = "/sse", produces = "text/event-stream") |
| | | @ApiOperation("å建SSEè¿æ¥ï¼çå¬ä»»å¡ç¶æåå") |
| | | public SseEmitter createConnection(@RequestParam(required = false) String taskId) { |
| | | SseEmitter emitter = notificationService.createConnection(taskId); |
| | | if (emitter == null) { |
| | | throw new RuntimeException("å建SSEè¿æ¥å¤±è´¥"); |
| | | } |
| | | return emitter; |
| | | } |
| | | |
| | | @GetMapping(value = "/sse/all", produces = "text/event-stream") |
| | | @ApiOperation("å建SSEè¿æ¥ï¼ç嬿æä»»å¡ç¶æåå") |
| | | public SseEmitter createConnectionForAllTasks() { |
| | | return createConnection(null); |
| | | } |
| | | |
| | | @PostMapping("/close/{taskId}") |
| | | @ApiOperation("å
³éæå®ä»»å¡çSSEè¿æ¥") |
| | | public Result<Boolean> closeConnections(@PathVariable String taskId) { |
| | | notificationService.closeConnections(taskId); |
| | | return Result.success(true); |
| | | } |
| | | |
| | | @PostMapping("/close/all") |
| | | @ApiOperation("å
³éææSSEè¿æ¥") |
| | | public Result<Boolean> closeAllConnections() { |
| | | notificationService.closeAllConnections(); |
| | | return Result.success(true); |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.mes.task.model; |
| | | |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * éè¯çç¥é
ç½® |
| | | * |
| | | * @author mes |
| | | * @since 2025-01-XX |
| | | */ |
| | | @Data |
| | | @Builder |
| | | public class RetryPolicy { |
| | | |
| | | /** |
| | | * æå¤§éè¯æ¬¡æ° |
| | | */ |
| | | @Builder.Default |
| | | private int maxRetryCount = 3; |
| | | |
| | | /** |
| | | * åå§éè¯é´éï¼æ¯«ç§ï¼ |
| | | */ |
| | | @Builder.Default |
| | | private long initialRetryIntervalMs = 1000; |
| | | |
| | | /** |
| | | * éè¯é´éå¢é¿åæ°ï¼ææ°éé¿ï¼ |
| | | */ |
| | | @Builder.Default |
| | | private double backoffMultiplier = 2.0; |
| | | |
| | | /** |
| | | * æå¤§éè¯é´éï¼æ¯«ç§ï¼ |
| | | */ |
| | | @Builder.Default |
| | | private long maxRetryIntervalMs = 30000; |
| | | |
| | | /** |
| | | * æ¯å¦å¯ç¨ææ°éé¿ |
| | | */ |
| | | @Builder.Default |
| | | private boolean enableExponentialBackoff = true; |
| | | |
| | | /** |
| | | * å¯éè¯çå¼å¸¸ç±»åï¼ç±»ååè¡¨ï¼ |
| | | */ |
| | | private java.util.List<String> retryableExceptions; |
| | | |
| | | /** |
| | | * ä¸å¯éè¯çå¼å¸¸ç±»åï¼ç±»ååè¡¨ï¼ |
| | | */ |
| | | private java.util.List<String> nonRetryableExceptions; |
| | | |
| | | /** |
| | | * é»è®¤éè¯çç¥ |
| | | */ |
| | | public static RetryPolicy defaultPolicy() { |
| | | return RetryPolicy.builder() |
| | | .maxRetryCount(3) |
| | | .initialRetryIntervalMs(1000) |
| | | .backoffMultiplier(2.0) |
| | | .maxRetryIntervalMs(30000) |
| | | .enableExponentialBackoff(true) |
| | | .build(); |
| | | } |
| | | |
| | | /** |
| | | * 计ç®éè¯é´é |
| | | * |
| | | * @param retryAttempt å½åéè¯æ¬¡æ°ï¼ä»1å¼å§ï¼ |
| | | * @return éè¯é´éï¼æ¯«ç§ï¼ |
| | | */ |
| | | public long calculateRetryInterval(int retryAttempt) { |
| | | if (!enableExponentialBackoff) { |
| | | return initialRetryIntervalMs; |
| | | } |
| | | |
| | | long interval = (long) (initialRetryIntervalMs * Math.pow(backoffMultiplier, retryAttempt - 1)); |
| | | return Math.min(interval, maxRetryIntervalMs); |
| | | } |
| | | |
| | | /** |
| | | * 夿å¼å¸¸æ¯å¦å¯éè¯ |
| | | * |
| | | * @param exception å¼å¸¸å¯¹è±¡ |
| | | * @return æ¯å¦å¯éè¯ |
| | | */ |
| | | public boolean isRetryable(Throwable exception) { |
| | | if (exception == null) { |
| | | return false; |
| | | } |
| | | |
| | | String exceptionClassName = exception.getClass().getName(); |
| | | |
| | | // æ£æ¥ä¸å¯éè¯å表 |
| | | if (nonRetryableExceptions != null) { |
| | | for (String nonRetryable : nonRetryableExceptions) { |
| | | if (exceptionClassName.contains(nonRetryable)) { |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // æ£æ¥å¯éè¯å表 |
| | | if (retryableExceptions != null && !retryableExceptions.isEmpty()) { |
| | | for (String retryable : retryableExceptions) { |
| | | if (exceptionClassName.contains(retryable)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; // 妿æå®äºå¯éè¯å表ï¼ä½å½åå¼å¸¸ä¸å¨å表ä¸ï¼åä¸å¯éè¯ |
| | | } |
| | | |
| | | // é»è®¤çç¥ï¼ç½ç»å¼å¸¸ãè¶
æ¶å¼å¸¸å¯éè¯ï¼å
¶ä»ä¸å¯éè¯ |
| | | return isDefaultRetryable(exception); |
| | | } |
| | | |
| | | /** |
| | | * é»è®¤éè¯å¤æé»è¾ |
| | | */ |
| | | private boolean isDefaultRetryable(Throwable exception) { |
| | | String exceptionClassName = exception.getClass().getName().toLowerCase(); |
| | | String message = exception.getMessage() != null ? exception.getMessage().toLowerCase() : ""; |
| | | |
| | | // ç½ç»ç¸å
³å¼å¸¸ |
| | | if (exceptionClassName.contains("timeout") || |
| | | exceptionClassName.contains("connection") || |
| | | exceptionClassName.contains("network") || |
| | | message.contains("timeout") || |
| | | message.contains("connection") || |
| | | message.contains("ç½ç»")) { |
| | | return true; |
| | | } |
| | | |
| | | // ä¸å¡å¼å¸¸é常ä¸å¯éè¯ |
| | | if (exceptionClassName.contains("illegalargument") || |
| | | exceptionClassName.contains("illegalstate") || |
| | | exceptionClassName.contains("validation")) { |
| | | return false; |
| | | } |
| | | |
| | | // å
¶ä»å¼å¸¸é»è®¤ä¸å¯éè¯ |
| | | return false; |
| | | } |
| | | } |
| | | |
| | |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.fasterxml.jackson.core.JsonProcessingException; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.mes.device.entity.DeviceConfig; |
| | | import com.mes.device.entity.DeviceGroupConfig; |
| | |
| | | import com.mes.interaction.DeviceLogicHandlerFactory; |
| | | import com.mes.interaction.base.InteractionContext; |
| | | import com.mes.interaction.base.InteractionResult; |
| | | import com.mes.device.service.DeviceCoordinationService; |
| | | import com.mes.device.service.DeviceInteractionService; |
| | | import com.mes.task.dto.TaskParameters; |
| | | import com.mes.task.entity.MultiDeviceTask; |
| | | import com.mes.task.entity.TaskStepDetail; |
| | | import com.mes.task.mapper.MultiDeviceTaskMapper; |
| | | import com.mes.task.mapper.TaskStepDetailMapper; |
| | | import com.mes.task.model.RetryPolicy; |
| | | import com.mes.task.model.TaskExecutionContext; |
| | | import com.mes.task.model.TaskExecutionResult; |
| | | import com.mes.task.service.TaskStatusNotificationService; |
| | | import com.mes.device.vo.DevicePlcVO; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.util.*; |
| | | import java.util.concurrent.*; |
| | | |
| | | /** |
| | | * å¤è®¾å¤ä»»å¡æ§è¡å¼æ |
| | | * æ¯æä¸²è¡åå¹¶è¡ä¸¤ç§æ§è¡æ¨¡å¼ |
| | | */ |
| | | @Slf4j |
| | | @Component |
| | |
| | | public class TaskExecutionEngine { |
| | | |
| | | private static final Map<String, String> DEFAULT_OPERATIONS = new HashMap<>(); |
| | | private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {}; |
| | | |
| | | // æ§è¡æ¨¡å¼å¸¸é |
| | | private static final String EXECUTION_MODE_SERIAL = "SERIAL"; |
| | | private static final String EXECUTION_MODE_PARALLEL = "PARALLEL"; |
| | | |
| | | static { |
| | | DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.LOAD_VEHICLE, "feedGlass"); |
| | |
| | | private final DeviceInteractionService deviceInteractionService; |
| | | private final DeviceInteractionRegistry interactionRegistry; |
| | | private final DeviceLogicHandlerFactory handlerFactory; |
| | | private final DeviceCoordinationService deviceCoordinationService; |
| | | private final TaskStatusNotificationService notificationService; |
| | | private final ObjectMapper objectMapper; |
| | | |
| | | // çº¿ç¨æ± ç¨äºå¹¶è¡æ§è¡ |
| | | private final ExecutorService executorService = Executors.newCachedThreadPool(r -> { |
| | | Thread t = new Thread(r, "TaskExecutionEngine-Parallel"); |
| | | t.setDaemon(true); |
| | | return t; |
| | | }); |
| | | |
| | | public TaskExecutionResult execute(MultiDeviceTask task, |
| | | DeviceGroupConfig groupConfig, |
| | |
| | | } |
| | | |
| | | TaskExecutionContext context = new TaskExecutionContext(parameters); |
| | | |
| | | // 设å¤åè°ï¼æ£æ¥ä¾èµå
³ç³»åæ§è¡æ¡ä»¶ |
| | | DeviceCoordinationService.CoordinationResult coordinationResult = |
| | | deviceCoordinationService.coordinateExecution(groupConfig, devices, context); |
| | | if (!coordinationResult.canExecute()) { |
| | | log.warn("设å¤åè°å¤±è´¥: {}", coordinationResult.getMessage()); |
| | | return TaskExecutionResult.failure(coordinationResult.getMessage(), Collections.emptyMap()); |
| | | } |
| | | log.info("设å¤åè°æå: {}", coordinationResult.getMessage()); |
| | | |
| | | task.setTotalSteps(devices.size()); |
| | | task.setStatus(MultiDeviceTask.Status.RUNNING.name()); |
| | | multiDeviceTaskMapper.updateById(task); |
| | | |
| | | // éç¥ä»»å¡å¼å§æ§è¡ |
| | | notificationService.notifyTaskStatus(task); |
| | | |
| | | // ç¡®å®æ§è¡æ¨¡å¼ |
| | | String executionMode = determineExecutionMode(groupConfig); |
| | | Integer maxConcurrent = getMaxConcurrentDevices(groupConfig); |
| | | |
| | | List<Map<String, Object>> stepSummaries = new ArrayList<>(); |
| | | boolean success = true; |
| | | String failureMessage = null; |
| | | log.info("任塿§è¡æ¨¡å¼: {}, æå¤§å¹¶åæ°: {}, è®¾å¤æ°: {}", executionMode, maxConcurrent, devices.size()); |
| | | |
| | | for (int i = 0; i < devices.size(); i++) { |
| | | DeviceConfig device = devices.get(i); |
| | | int order = i + 1; |
| | | TaskStepDetail step = createStepRecord(task, device, order); |
| | | StepResult stepResult = executeStep(task, step, device, context); |
| | | stepSummaries.add(stepResult.toSummary()); |
| | | if (!stepResult.isSuccess()) { |
| | | success = false; |
| | | failureMessage = stepResult.getMessage(); |
| | | break; |
| | | List<Map<String, Object>> stepSummaries; |
| | | boolean success; |
| | | String failureMessage; |
| | | |
| | | if (EXECUTION_MODE_PARALLEL.equals(executionMode)) { |
| | | // å¹¶è¡æ§è¡æ¨¡å¼ |
| | | stepSummaries = new ArrayList<>(Collections.nCopies(devices.size(), null)); |
| | | Pair<Boolean, String> result = executeParallel(task, devices, context, stepSummaries, maxConcurrent); |
| | | success = result.getFirst(); |
| | | failureMessage = result.getSecond(); |
| | | } else { |
| | | // ä¸²è¡æ§è¡æ¨¡å¼ï¼é»è®¤ï¼ |
| | | stepSummaries = new ArrayList<>(); |
| | | success = true; |
| | | failureMessage = null; |
| | | for (int i = 0; i < devices.size(); i++) { |
| | | DeviceConfig device = devices.get(i); |
| | | int order = i + 1; |
| | | TaskStepDetail step = createStepRecord(task, device, order); |
| | | StepResult stepResult = executeStep(task, step, device, context); |
| | | stepSummaries.add(stepResult.toSummary()); |
| | | if (!stepResult.isSuccess()) { |
| | | success = false; |
| | | failureMessage = stepResult.getMessage(); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | payload.put("steps", stepSummaries); |
| | | payload.put("groupId", groupConfig.getId()); |
| | | payload.put("deviceCount", devices.size()); |
| | | payload.put("executionMode", executionMode); |
| | | |
| | | // æ´æ°ä»»å¡æç»ç¶æ |
| | | if (success) { |
| | | task.setStatus(MultiDeviceTask.Status.COMPLETED.name()); |
| | | } else { |
| | | task.setStatus(MultiDeviceTask.Status.FAILED.name()); |
| | | task.setErrorMessage(failureMessage); |
| | | } |
| | | task.setEndTime(new Date()); |
| | | multiDeviceTaskMapper.updateById(task); |
| | | |
| | | // éç¥ä»»å¡å®æ |
| | | notificationService.notifyTaskStatus(task); |
| | | |
| | | if (success) { |
| | | return TaskExecutionResult.success(payload); |
| | | } |
| | | return TaskExecutionResult.failure(failureMessage != null ? failureMessage : "任塿§è¡å¤±è´¥", payload); |
| | | } |
| | | |
| | | /** |
| | | * å¹¶è¡æ§è¡å¤ä¸ªè®¾å¤æä½ |
| | | */ |
| | | private Pair<Boolean, String> executeParallel(MultiDeviceTask task, |
| | | List<DeviceConfig> devices, |
| | | TaskExecutionContext context, |
| | | List<Map<String, Object>> stepSummaries, |
| | | Integer maxConcurrent) { |
| | | int concurrency = maxConcurrent != null && maxConcurrent > 0 |
| | | ? Math.min(maxConcurrent, devices.size()) |
| | | : devices.size(); |
| | | |
| | | // åå»ºæææ¥éª¤è®°å½ |
| | | List<TaskStepDetail> steps = new ArrayList<>(); |
| | | for (int i = 0; i < devices.size(); i++) { |
| | | DeviceConfig device = devices.get(i); |
| | | int order = i + 1; |
| | | TaskStepDetail step = createStepRecord(task, device, order); |
| | | steps.add(step); |
| | | } |
| | | |
| | | // 使ç¨ä¿¡å·éæ§å¶å¹¶åæ° |
| | | Semaphore semaphore = new Semaphore(concurrency); |
| | | List<CompletableFuture<StepResult>> futures = new ArrayList<>(); |
| | | |
| | | for (int i = 0; i < devices.size(); i++) { |
| | | final int index = i; |
| | | final DeviceConfig device = devices.get(index); |
| | | final TaskStepDetail step = steps.get(index); |
| | | |
| | | CompletableFuture<StepResult> future = CompletableFuture.supplyAsync(() -> { |
| | | try { |
| | | semaphore.acquire(); |
| | | try { |
| | | return executeStep(task, step, device, context); |
| | | } finally { |
| | | semaphore.release(); |
| | | } |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.error("å¹¶è¡æ§è¡è¢«ä¸æ, deviceId={}", device.getId(), e); |
| | | return StepResult.failure(device.getDeviceName(), "æ§è¡è¢«ä¸æ"); |
| | | } catch (Exception e) { |
| | | log.error("å¹¶è¡æ§è¡å¼å¸¸, deviceId={}", device.getId(), e); |
| | | return StepResult.failure(device.getDeviceName(), e.getMessage()); |
| | | } |
| | | }, executorService); |
| | | |
| | | final int finalIndex = index; |
| | | future.whenComplete((result, throwable) -> { |
| | | if (throwable != null) { |
| | | log.error("å¹¶è¡æ§è¡å®ææ¶å¼å¸¸, deviceId={}", device.getId(), throwable); |
| | | stepSummaries.set(finalIndex, StepResult.failure(device.getDeviceName(), |
| | | throwable.getMessage()).toSummary()); |
| | | } else if (result != null) { |
| | | stepSummaries.set(finalIndex, result.toSummary()); |
| | | } |
| | | }); |
| | | |
| | | futures.add(future); |
| | | } |
| | | |
| | | // çå¾
ææä»»å¡å®æ |
| | | CompletableFuture<Void> allFutures = CompletableFuture.allOf( |
| | | futures.toArray(new CompletableFuture[0]) |
| | | ); |
| | | |
| | | try { |
| | | allFutures.get(30, TimeUnit.MINUTES); // æå¤çå¾
30åé |
| | | } catch (TimeoutException e) { |
| | | log.error("å¹¶è¡æ§è¡è¶
æ¶, taskId={}", task.getTaskId(), e); |
| | | return Pair.of(false, "任塿§è¡è¶
æ¶"); |
| | | } catch (Exception e) { |
| | | log.error("çå¾
å¹¶è¡æ§è¡å®ææ¶å¼å¸¸, taskId={}", task.getTaskId(), e); |
| | | return Pair.of(false, "çå¾
æ§è¡å®ææ¶åçå¼å¸¸: " + e.getMessage()); |
| | | } |
| | | |
| | | // æ£æ¥æææ¥éª¤çæ§è¡ç»æ |
| | | boolean allSuccess = true; |
| | | String firstFailureMessage = null; |
| | | for (int i = 0; i < futures.size(); i++) { |
| | | try { |
| | | StepResult result = futures.get(i).get(); |
| | | if (result != null && !result.isSuccess()) { |
| | | allSuccess = false; |
| | | if (firstFailureMessage == null) { |
| | | firstFailureMessage = result.getMessage(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("è·åæ¥éª¤æ§è¡ç»æå¼å¸¸, stepIndex={}", i, e); |
| | | allSuccess = false; |
| | | if (firstFailureMessage == null) { |
| | | firstFailureMessage = "è·åæ§è¡ç»æå¼å¸¸: " + e.getMessage(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return Pair.of(allSuccess, firstFailureMessage); |
| | | } |
| | | |
| | | /** |
| | | * ç¡®å®æ§è¡æ¨¡å¼ |
| | | */ |
| | | private String determineExecutionMode(DeviceGroupConfig groupConfig) { |
| | | if (groupConfig == null) { |
| | | return EXECUTION_MODE_SERIAL; // é»è®¤ä¸²è¡ |
| | | } |
| | | |
| | | // ä»extraConfigä¸è¯»åexecutionMode |
| | | String extraConfig = groupConfig.getExtraConfig(); |
| | | if (StringUtils.hasText(extraConfig)) { |
| | | try { |
| | | Map<String, Object> config = objectMapper.readValue(extraConfig, MAP_TYPE); |
| | | Object mode = config.get("executionMode"); |
| | | if (mode != null) { |
| | | String modeStr = String.valueOf(mode).toUpperCase(); |
| | | if (EXECUTION_MODE_PARALLEL.equals(modeStr) || EXECUTION_MODE_SERIAL.equals(modeStr)) { |
| | | return modeStr; |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("è§£æè®¾å¤ç»æ§è¡æ¨¡å¼å¤±è´¥, groupId={}", groupConfig.getId(), e); |
| | | } |
| | | } |
| | | |
| | | // 妿æmaxConcurrentDevicesä¸å¤§äº1ï¼é»è®¤ä½¿ç¨å¹¶è¡æ¨¡å¼ |
| | | if (groupConfig.getMaxConcurrentDevices() != null && groupConfig.getMaxConcurrentDevices() > 1) { |
| | | return EXECUTION_MODE_PARALLEL; |
| | | } |
| | | |
| | | return EXECUTION_MODE_SERIAL; // é»è®¤ä¸²è¡ |
| | | } |
| | | |
| | | /** |
| | | * è·åæå¤§å¹¶åè®¾å¤æ° |
| | | */ |
| | | private Integer getMaxConcurrentDevices(DeviceGroupConfig groupConfig) { |
| | | if (groupConfig == null) { |
| | | return 1; |
| | | } |
| | | |
| | | // ä»extraConfigä¸è¯»åmaxConcurrent |
| | | String extraConfig = groupConfig.getExtraConfig(); |
| | | if (StringUtils.hasText(extraConfig)) { |
| | | try { |
| | | Map<String, Object> config = objectMapper.readValue(extraConfig, MAP_TYPE); |
| | | Object maxConcurrent = config.get("maxConcurrent"); |
| | | if (maxConcurrent instanceof Number) { |
| | | return ((Number) maxConcurrent).intValue(); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("è§£æè®¾å¤ç»æå¤§å¹¶åæ°å¤±è´¥, groupId={}", groupConfig.getId(), e); |
| | | } |
| | | } |
| | | |
| | | // 使ç¨å®ä½å段 |
| | | return groupConfig.getMaxConcurrentDevices() != null && groupConfig.getMaxConcurrentDevices() > 0 |
| | | ? groupConfig.getMaxConcurrentDevices() |
| | | : 1; |
| | | } |
| | | |
| | | /** |
| | | * ç®åçPairç±»ç¨äºè¿åä¸¤ä¸ªå¼ |
| | | */ |
| | | private static class Pair<T, U> { |
| | | private final T first; |
| | | private final U second; |
| | | |
| | | private Pair(T first, U second) { |
| | | this.first = first; |
| | | this.second = second; |
| | | } |
| | | |
| | | public static <T, U> Pair<T, U> of(T first, U second) { |
| | | return new Pair<>(first, second); |
| | | } |
| | | |
| | | public T getFirst() { |
| | | return first; |
| | | } |
| | | |
| | | public U getSecond() { |
| | | return second; |
| | | } |
| | | } |
| | | |
| | | private TaskStepDetail createStepRecord(MultiDeviceTask task, DeviceConfig device, int order) { |
| | |
| | | TaskStepDetail step, |
| | | DeviceConfig device, |
| | | TaskExecutionContext context) { |
| | | return executeStepWithRetry(task, step, device, context, getRetryPolicy(device)); |
| | | } |
| | | |
| | | /** |
| | | * 带éè¯çæ¥éª¤æ§è¡ |
| | | */ |
| | | private StepResult executeStepWithRetry(MultiDeviceTask task, |
| | | TaskStepDetail step, |
| | | DeviceConfig device, |
| | | TaskExecutionContext context, |
| | | RetryPolicy retryPolicy) { |
| | | Date startTime = new Date(); |
| | | step.setStartTime(startTime); |
| | | step.setStatus(TaskStepDetail.Status.RUNNING.name()); |
| | | step.setRetryCount(0); |
| | | |
| | | DeviceInteraction deviceInteraction = interactionRegistry.getInteraction(device.getDeviceType()); |
| | | if (deviceInteraction != null) { |
| | | return executeInteractionStep(task, step, device, context, deviceInteraction); |
| | | return executeInteractionStepWithRetry(task, step, device, context, deviceInteraction, retryPolicy); |
| | | } |
| | | |
| | | Map<String, Object> params = buildOperationParams(device, context); |
| | |
| | | |
| | | String operation = determineOperation(device, params); |
| | | DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType()); |
| | | DevicePlcVO.OperationResult result; |
| | | |
| | | int retryAttempt = 0; |
| | | Exception lastException = null; |
| | | |
| | | while (retryAttempt <= retryPolicy.getMaxRetryCount()) { |
| | | try { |
| | | if (retryAttempt > 0) { |
| | | // éè¯åçå¾
|
| | | long waitTime = retryPolicy.calculateRetryInterval(retryAttempt); |
| | | log.info("æ¥éª¤æ§è¡éè¯: deviceId={}, operation={}, retryAttempt={}/{}, waitTime={}ms", |
| | | device.getId(), operation, retryAttempt, retryPolicy.getMaxRetryCount(), waitTime); |
| | | Thread.sleep(waitTime); |
| | | |
| | | // æ´æ°æ¥éª¤ç¶æ |
| | | step.setRetryCount(retryAttempt); |
| | | step.setStatus(TaskStepDetail.Status.RUNNING.name()); |
| | | step.setStartTime(new Date()); |
| | | taskStepDetailMapper.updateById(step); |
| | | } |
| | | |
| | | try { |
| | | if (handler == null) { |
| | | result = deviceInteractionService.executeOperation(device.getId(), operation, params); |
| | | } else { |
| | | result = handler.execute(device, operation, params); |
| | | DevicePlcVO.OperationResult result; |
| | | if (handler == null) { |
| | | result = deviceInteractionService.executeOperation(device.getId(), operation, params); |
| | | } else { |
| | | result = handler.execute(device, operation, params); |
| | | } |
| | | |
| | | boolean opSuccess = Boolean.TRUE.equals(result.getSuccess()); |
| | | updateStepAfterOperation(step, result, opSuccess); |
| | | updateTaskProgress(task, step.getStepOrder(), opSuccess); |
| | | |
| | | // éç¥æ¥éª¤æ´æ° |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | |
| | | if (opSuccess) { |
| | | updateContextAfterSuccess(device, context, params); |
| | | |
| | | // åæ¥è®¾å¤ç¶æ |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.COMPLETED, context); |
| | | |
| | | return StepResult.success(device.getDeviceName(), result.getMessage()); |
| | | } else { |
| | | // ä¸å¡å¤±è´¥ï¼å¤ææ¯å¦å¯éè¯ |
| | | if (retryAttempt < retryPolicy.getMaxRetryCount() && isRetryableFailure(result)) { |
| | | retryAttempt++; |
| | | lastException = new RuntimeException(result.getMessage()); |
| | | log.warn("æ¥éª¤æ§è¡å¤±è´¥ï¼åå¤éè¯: deviceId={}, operation={}, retryAttempt={}, message={}", |
| | | device.getId(), operation, retryAttempt, result.getMessage()); |
| | | continue; |
| | | } |
| | | |
| | | // åæ¥å¤±è´¥ç¶æ |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.FAILED, context); |
| | | |
| | | return StepResult.failure(device.getDeviceName(), result.getMessage()); |
| | | } |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.error("æ¥éª¤æ§è¡è¢«ä¸æ, deviceId={}, operation={}", device.getId(), operation, e); |
| | | step.setStatus(TaskStepDetail.Status.FAILED.name()); |
| | | step.setErrorMessage("æ§è¡è¢«ä¸æ: " + e.getMessage()); |
| | | step.setEndTime(new Date()); |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | step.setRetryCount(retryAttempt); |
| | | taskStepDetailMapper.updateById(step); |
| | | updateTaskProgress(task, step.getStepOrder(), false); |
| | | return StepResult.failure(device.getDeviceName(), "æ§è¡è¢«ä¸æ"); |
| | | } catch (Exception e) { |
| | | lastException = e; |
| | | log.error("è®¾å¤æä½å¼å¸¸, deviceId={}, operation={}, retryAttempt={}", |
| | | device.getId(), operation, retryAttempt, e); |
| | | |
| | | // 夿æ¯å¦å¯éè¯ |
| | | if (retryAttempt < retryPolicy.getMaxRetryCount() && retryPolicy.isRetryable(e)) { |
| | | retryAttempt++; |
| | | log.warn("æ¥éª¤æ§è¡å¼å¸¸ï¼åå¤éè¯: deviceId={}, operation={}, retryAttempt={}, exception={}", |
| | | device.getId(), operation, retryAttempt, e.getClass().getSimpleName()); |
| | | continue; |
| | | } |
| | | |
| | | // ä¸å¯éè¯æè¾¾å°æå¤§éè¯æ¬¡æ° |
| | | step.setStatus(TaskStepDetail.Status.FAILED.name()); |
| | | step.setErrorMessage(e.getMessage()); |
| | | step.setEndTime(new Date()); |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | step.setRetryCount(retryAttempt); |
| | | taskStepDetailMapper.updateById(step); |
| | | updateTaskProgress(task, step.getStepOrder(), false); |
| | | |
| | | // éç¥æ¥éª¤æ´æ° |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | |
| | | // åæ¥å¤±è´¥ç¶æ |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.FAILED, context); |
| | | |
| | | String errorMsg = retryAttempt > 0 |
| | | ? String.format("æ§è¡å¤±è´¥ï¼å·²éè¯%d次ï¼: %s", retryAttempt, e.getMessage()) |
| | | : e.getMessage(); |
| | | return StepResult.failure(device.getDeviceName(), errorMsg); |
| | | } |
| | | |
| | | boolean opSuccess = Boolean.TRUE.equals(result.getSuccess()); |
| | | updateStepAfterOperation(step, result, opSuccess); |
| | | updateTaskProgress(task, step.getStepOrder(), opSuccess); |
| | | |
| | | if (opSuccess) { |
| | | updateContextAfterSuccess(device, context, params); |
| | | return StepResult.success(device.getDeviceName(), result.getMessage()); |
| | | } |
| | | return StepResult.failure(device.getDeviceName(), result.getMessage()); |
| | | } catch (Exception e) { |
| | | log.error("è®¾å¤æä½å¼å¸¸, deviceId={}, operation={}", device.getId(), operation, e); |
| | | step.setStatus(TaskStepDetail.Status.FAILED.name()); |
| | | step.setErrorMessage(e.getMessage()); |
| | | step.setEndTime(new Date()); |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | taskStepDetailMapper.updateById(step); |
| | | updateTaskProgress(task, step.getStepOrder(), false); |
| | | return StepResult.failure(device.getDeviceName(), e.getMessage()); |
| | | } |
| | | |
| | | // è¾¾å°æå¤§éè¯æ¬¡æ° |
| | | step.setStatus(TaskStepDetail.Status.FAILED.name()); |
| | | step.setErrorMessage(lastException != null ? lastException.getMessage() : "æ§è¡å¤±è´¥"); |
| | | step.setEndTime(new Date()); |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | step.setRetryCount(retryAttempt); |
| | | taskStepDetailMapper.updateById(step); |
| | | updateTaskProgress(task, step.getStepOrder(), false); |
| | | |
| | | // éç¥æ¥éª¤æ´æ° |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.FAILED, context); |
| | | |
| | | return StepResult.failure(device.getDeviceName(), |
| | | String.format("æ§è¡å¤±è´¥ï¼å·²éè¯%d次ï¼", retryAttempt)); |
| | | } |
| | | |
| | | private StepResult executeInteractionStep(MultiDeviceTask task, |
| | | TaskStepDetail step, |
| | | DeviceConfig device, |
| | | TaskExecutionContext context, |
| | | DeviceInteraction deviceInteraction) { |
| | | try { |
| | | InteractionContext interactionContext = new InteractionContext(device, context); |
| | | step.setInputData(toJson(context.getParameters())); |
| | | InteractionResult interactionResult = deviceInteraction.execute(interactionContext); |
| | | boolean success = interactionResult != null && interactionResult.isSuccess(); |
| | | updateStepAfterInteraction(step, interactionResult); |
| | | updateTaskProgress(task, step.getStepOrder(), success); |
| | | /** |
| | | * 带éè¯çäº¤äºæ¥éª¤æ§è¡ |
| | | */ |
| | | private StepResult executeInteractionStepWithRetry(MultiDeviceTask task, |
| | | TaskStepDetail step, |
| | | DeviceConfig device, |
| | | TaskExecutionContext context, |
| | | DeviceInteraction deviceInteraction, |
| | | RetryPolicy retryPolicy) { |
| | | int retryAttempt = 0; |
| | | Exception lastException = null; |
| | | |
| | | while (retryAttempt <= retryPolicy.getMaxRetryCount()) { |
| | | try { |
| | | if (retryAttempt > 0) { |
| | | long waitTime = retryPolicy.calculateRetryInterval(retryAttempt); |
| | | log.info("äº¤äºæ¥éª¤æ§è¡éè¯: deviceId={}, retryAttempt={}/{}, waitTime={}ms", |
| | | device.getId(), retryAttempt, retryPolicy.getMaxRetryCount(), waitTime); |
| | | Thread.sleep(waitTime); |
| | | |
| | | step.setRetryCount(retryAttempt); |
| | | step.setStatus(TaskStepDetail.Status.RUNNING.name()); |
| | | step.setStartTime(new Date()); |
| | | taskStepDetailMapper.updateById(step); |
| | | } |
| | | |
| | | if (success) { |
| | | return StepResult.success(device.getDeviceName(), interactionResult.getMessage()); |
| | | InteractionContext interactionContext = new InteractionContext(device, context); |
| | | step.setInputData(toJson(context.getParameters())); |
| | | InteractionResult interactionResult = deviceInteraction.execute(interactionContext); |
| | | boolean success = interactionResult != null && interactionResult.isSuccess(); |
| | | updateStepAfterInteraction(step, interactionResult); |
| | | updateTaskProgress(task, step.getStepOrder(), success); |
| | | |
| | | if (success) { |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.COMPLETED, context); |
| | | return StepResult.success(device.getDeviceName(), interactionResult.getMessage()); |
| | | } else { |
| | | if (retryAttempt < retryPolicy.getMaxRetryCount()) { |
| | | retryAttempt++; |
| | | continue; |
| | | } |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.FAILED, context); |
| | | String message = interactionResult != null ? interactionResult.getMessage() : "äº¤äºæ§è¡å¤±è´¥"; |
| | | return StepResult.failure(device.getDeviceName(), message); |
| | | } |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | log.error("äº¤äºæ¥éª¤æ§è¡è¢«ä¸æ, deviceId={}", device.getId(), e); |
| | | step.setStatus(TaskStepDetail.Status.FAILED.name()); |
| | | step.setErrorMessage("æ§è¡è¢«ä¸æ: " + e.getMessage()); |
| | | step.setEndTime(new Date()); |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | step.setRetryCount(retryAttempt); |
| | | taskStepDetailMapper.updateById(step); |
| | | updateTaskProgress(task, step.getStepOrder(), false); |
| | | return StepResult.failure(device.getDeviceName(), "æ§è¡è¢«ä¸æ"); |
| | | } catch (Exception e) { |
| | | lastException = e; |
| | | log.error("äº¤äºæ§è¡å¼å¸¸, deviceId={}, retryAttempt={}", device.getId(), retryAttempt, e); |
| | | |
| | | if (retryAttempt < retryPolicy.getMaxRetryCount() && retryPolicy.isRetryable(e)) { |
| | | retryAttempt++; |
| | | continue; |
| | | } |
| | | |
| | | step.setStatus(TaskStepDetail.Status.FAILED.name()); |
| | | step.setErrorMessage(e.getMessage()); |
| | | step.setEndTime(new Date()); |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | step.setRetryCount(retryAttempt); |
| | | taskStepDetailMapper.updateById(step); |
| | | updateTaskProgress(task, step.getStepOrder(), false); |
| | | |
| | | // éç¥æ¥éª¤æ´æ° |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.FAILED, context); |
| | | |
| | | String errorMsg = retryAttempt > 0 |
| | | ? String.format("æ§è¡å¤±è´¥ï¼å·²éè¯%d次ï¼: %s", retryAttempt, e.getMessage()) |
| | | : e.getMessage(); |
| | | return StepResult.failure(device.getDeviceName(), errorMsg); |
| | | } |
| | | String message = interactionResult != null ? interactionResult.getMessage() : "äº¤äºæ§è¡å¤±è´¥"; |
| | | return StepResult.failure(device.getDeviceName(), message); |
| | | } catch (Exception e) { |
| | | log.error("äº¤äºæ§è¡å¼å¸¸, deviceId={}", device.getId(), e); |
| | | step.setStatus(TaskStepDetail.Status.FAILED.name()); |
| | | step.setErrorMessage(e.getMessage()); |
| | | step.setEndTime(new Date()); |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | taskStepDetailMapper.updateById(step); |
| | | updateTaskProgress(task, step.getStepOrder(), false); |
| | | return StepResult.failure(device.getDeviceName(), e.getMessage()); |
| | | } |
| | | |
| | | step.setStatus(TaskStepDetail.Status.FAILED.name()); |
| | | step.setErrorMessage(lastException != null ? lastException.getMessage() : "äº¤äºæ§è¡å¤±è´¥"); |
| | | step.setEndTime(new Date()); |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | step.setRetryCount(retryAttempt); |
| | | taskStepDetailMapper.updateById(step); |
| | | updateTaskProgress(task, step.getStepOrder(), false); |
| | | |
| | | // éç¥æ¥éª¤æ´æ° |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.FAILED, context); |
| | | |
| | | return StepResult.failure(device.getDeviceName(), |
| | | String.format("æ§è¡å¤±è´¥ï¼å·²éè¯%d次ï¼", retryAttempt)); |
| | | } |
| | | |
| | | /** |
| | | * è·åéè¯çç¥ |
| | | */ |
| | | private RetryPolicy getRetryPolicy(DeviceConfig device) { |
| | | // å¯ä»¥ä»è®¾å¤é
ç½®ä¸è¯»åéè¯çç¥ |
| | | // ææ¶ä½¿ç¨é»è®¤çç¥ |
| | | return RetryPolicy.defaultPolicy(); |
| | | } |
| | | |
| | | /** |
| | | * 夿ä¸å¡å¤±è´¥æ¯å¦å¯éè¯ |
| | | */ |
| | | private boolean isRetryableFailure(DevicePlcVO.OperationResult result) { |
| | | if (result == null || result.getMessage() == null) { |
| | | return false; |
| | | } |
| | | String message = result.getMessage().toLowerCase(); |
| | | // ç½ç»é误ãè¶
æ¶é误å¯éè¯ |
| | | return message.contains("timeout") || |
| | | message.contains("connection") || |
| | | message.contains("ç½ç»") || |
| | | message.contains("è¶
æ¶"); |
| | | } |
| | | |
| | | |
| | | private void updateStepAfterOperation(TaskStepDetail step, |
| | | DevicePlcVO.OperationResult result, |
| | |
| | | update.set(MultiDeviceTask::getStatus, MultiDeviceTask.Status.FAILED.name()); |
| | | } |
| | | multiDeviceTaskMapper.update(null, update); |
| | | |
| | | // éç¥ä»»å¡ç¶ææ´æ° |
| | | notificationService.notifyTaskStatus(task); |
| | | } |
| | | |
| | | private String determineOperation(DeviceConfig device, Map<String, Object> params) { |
| | |
| | | private void updateContextAfterSuccess(DeviceConfig device, |
| | | TaskExecutionContext context, |
| | | Map<String, Object> params) { |
| | | List<String> glassIds = extractGlassIds(params); |
| | | |
| | | switch (device.getDeviceType()) { |
| | | case DeviceConfig.DeviceType.LOAD_VEHICLE: |
| | | context.setLoadedGlassIds(extractGlassIds(params)); |
| | | context.setLoadedGlassIds(glassIds); |
| | | // æ°æ®ä¼ éï¼ä¸å¤§è½¦ -> ä¸ä¸ä¸ªè®¾å¤ |
| | | if (!CollectionUtils.isEmpty(glassIds)) { |
| | | Map<String, Object> transferData = new HashMap<>(); |
| | | transferData.put("glassIds", glassIds); |
| | | transferData.put("sourceDevice", device.getDeviceCode()); |
| | | // è¿éç®åå¤çï¼å®é
åºè¯¥æ¾å°ä¸ä¸ä¸ªè®¾å¤ |
| | | // å¨ä¸²è¡æ¨¡å¼ä¸ï¼ä¸ä¸ä¸ªè®¾å¤ä¼å¨å¾ªç¯ä¸èªå¨è·å |
| | | } |
| | | break; |
| | | case DeviceConfig.DeviceType.LARGE_GLASS: |
| | | context.setProcessedGlassIds(extractGlassIds(params)); |
| | | context.setProcessedGlassIds(glassIds); |
| | | // æ°æ®ä¼ éï¼å¤§çç -> ä¸ä¸ä¸ªè®¾å¤ |
| | | if (!CollectionUtils.isEmpty(glassIds)) { |
| | | Map<String, Object> transferData = new HashMap<>(); |
| | | transferData.put("glassIds", glassIds); |
| | | transferData.put("sourceDevice", device.getDeviceCode()); |
| | | } |
| | | break; |
| | | default: |
| | | break; |
| New file |
| | |
| | | package com.mes.task.service; |
| | | |
| | | import com.mes.task.entity.MultiDeviceTask; |
| | | import com.mes.task.entity.TaskStepDetail; |
| | | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * ä»»å¡ç¶æéç¥æå¡ |
| | | * ç¨äºå®æ¶æ¨é任塿§è¡ç¶æ |
| | | * |
| | | * @author mes |
| | | * @since 2025-01-XX |
| | | */ |
| | | public interface TaskStatusNotificationService { |
| | | |
| | | /** |
| | | * å建SSEè¿æ¥ |
| | | * |
| | | * @param taskId ä»»å¡IDï¼å¯éï¼å¦æä¸ºnullåç嬿æä»»å¡ï¼ |
| | | * @return SSEåå°å¨ |
| | | */ |
| | | SseEmitter createConnection(String taskId); |
| | | |
| | | /** |
| | | * åéä»»å¡ç¶ææ´æ° |
| | | * |
| | | * @param task ä»»å¡å®ä½ |
| | | */ |
| | | void notifyTaskStatus(MultiDeviceTask task); |
| | | |
| | | /** |
| | | * åé任塿¥éª¤æ´æ° |
| | | * |
| | | * @param taskId ä»»å¡ID |
| | | * @param step æ¥éª¤è¯¦æ
|
| | | */ |
| | | void notifyStepUpdate(String taskId, TaskStepDetail step); |
| | | |
| | | /** |
| | | * åé任塿¥éª¤åè¡¨æ´æ° |
| | | * |
| | | * @param taskId ä»»å¡ID |
| | | * @param steps æ¥éª¤å表 |
| | | */ |
| | | void notifyStepsUpdate(String taskId, List<TaskStepDetail> steps); |
| | | |
| | | /** |
| | | * å
³éæå®ä»»å¡çè¿æ¥ |
| | | * |
| | | * @param taskId ä»»å¡ID |
| | | */ |
| | | void closeConnections(String taskId); |
| | | |
| | | /** |
| | | * å
³éææè¿æ¥ |
| | | */ |
| | | void closeAllConnections(); |
| | | } |
| | | |
| | |
| | | import com.mes.task.model.TaskExecutionResult; |
| | | import com.mes.task.service.MultiDeviceTaskService; |
| | | import com.mes.task.service.TaskExecutionEngine; |
| | | import com.mes.task.service.TaskStatusNotificationService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | private final DeviceGroupRelationMapper deviceGroupRelationMapper; |
| | | private final TaskStepDetailMapper taskStepDetailMapper; |
| | | private final TaskExecutionEngine taskExecutionEngine; |
| | | private final TaskStatusNotificationService notificationService; |
| | | private final ObjectMapper objectMapper; |
| | | |
| | | @Override |
| | |
| | | save(task); |
| | | |
| | | try { |
| | | // éç¥ä»»å¡å¼å§ |
| | | notificationService.notifyTaskStatus(task); |
| | | |
| | | TaskExecutionResult result = taskExecutionEngine.execute(task, groupConfig, devices, parameters); |
| | | task.setStatus(result.isSuccess() ? MultiDeviceTask.Status.COMPLETED.name() : MultiDeviceTask.Status.FAILED.name()); |
| | | task.setErrorMessage(result.isSuccess() ? null : result.getMessage()); |
| | | task.setEndTime(new Date()); |
| | | task.setResultData(writeJson(result.getData())); |
| | | updateById(task); |
| | | |
| | | // éç¥ä»»å¡å®æ |
| | | notificationService.notifyTaskStatus(task); |
| | | |
| | | return task; |
| | | } catch (Exception ex) { |
| | | log.error("å¤è®¾å¤ä»»å¡æ§è¡å¼å¸¸, taskId={}", task.getTaskId(), ex); |
| New file |
| | |
| | | package com.mes.task.service.impl; |
| | | |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.mes.task.entity.MultiDeviceTask; |
| | | import com.mes.task.entity.TaskStepDetail; |
| | | import com.mes.task.service.TaskStatusNotificationService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
| | | |
| | | import java.io.IOException; |
| | | import java.util.Collections; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.CopyOnWriteArrayList; |
| | | |
| | | /** |
| | | * ä»»å¡ç¶æéç¥æå¡å®ç° |
| | | * |
| | | * @author mes |
| | | * @since 2025-01-XX |
| | | */ |
| | | @Slf4j |
| | | @Service |
| | | public class TaskStatusNotificationServiceImpl implements TaskStatusNotificationService { |
| | | |
| | | private final ObjectMapper objectMapper = new ObjectMapper(); |
| | | |
| | | // å卿æSSEè¿æ¥ï¼taskId -> List<SseEmitter> |
| | | private final Map<String, List<SseEmitter>> connections = new ConcurrentHashMap<>(); |
| | | |
| | | // å卿æä»»å¡çè¿æ¥ï¼taskId为nullæ¶ä½¿ç¨ï¼ |
| | | private final List<SseEmitter> allTaskConnections = new CopyOnWriteArrayList<>(); |
| | | |
| | | // è¿æ¥è¶
æ¶æ¶é´ï¼æ¯«ç§ï¼ |
| | | private static final long TIMEOUT = 30 * 60 * 1000L; // 30åé |
| | | |
| | | @Override |
| | | public SseEmitter createConnection(String taskId) { |
| | | SseEmitter emitter = new SseEmitter(TIMEOUT); |
| | | |
| | | // è®¾ç½®å®æåè¶
æ¶åè° |
| | | emitter.onCompletion(() -> { |
| | | log.info("SSEè¿æ¥å®æ: taskId={}", taskId); |
| | | removeConnection(taskId, emitter); |
| | | }); |
| | | |
| | | emitter.onTimeout(() -> { |
| | | log.info("SSEè¿æ¥è¶
æ¶: taskId={}", taskId); |
| | | removeConnection(taskId, emitter); |
| | | }); |
| | | |
| | | emitter.onError((ex) -> { |
| | | log.error("SSEè¿æ¥é误: taskId={}", taskId, ex); |
| | | removeConnection(taskId, emitter); |
| | | }); |
| | | |
| | | // æ·»å å°è¿æ¥å表 |
| | | if (taskId != null && !taskId.isEmpty()) { |
| | | connections.computeIfAbsent(taskId, k -> new CopyOnWriteArrayList<>()).add(emitter); |
| | | } else { |
| | | allTaskConnections.add(emitter); |
| | | } |
| | | |
| | | try { |
| | | // åéåå§è¿æ¥æåæ¶æ¯ |
| | | Map<String, Object> initData = new HashMap<>(); |
| | | if (taskId != null) { |
| | | initData.put("taskId", taskId); |
| | | } |
| | | emitter.send(SseEmitter.event() |
| | | .name("connected") |
| | | .data(createMessage("è¿æ¥æå", initData))); |
| | | } catch (IOException e) { |
| | | log.error("åéåå§æ¶æ¯å¤±è´¥: taskId={}", taskId, e); |
| | | removeConnection(taskId, emitter); |
| | | return null; |
| | | } |
| | | |
| | | log.info("å建SSEè¿æ¥: taskId={}, å½åè¿æ¥æ°={}", taskId, getConnectionCount(taskId)); |
| | | return emitter; |
| | | } |
| | | |
| | | @Override |
| | | public void notifyTaskStatus(MultiDeviceTask task) { |
| | | if (task == null || task.getTaskId() == null) { |
| | | return; |
| | | } |
| | | |
| | | String taskId = task.getTaskId(); |
| | | Map<String, Object> data = createTaskStatusData(task); |
| | | |
| | | // åéç»æå®ä»»å¡çè¿æ¥ |
| | | sendToConnections(taskId, "taskStatus", data); |
| | | |
| | | // åéç»ææä»»å¡çè¿æ¥ |
| | | sendToAllTaskConnections("taskStatus", data); |
| | | |
| | | log.debug("æ¨éä»»å¡ç¶æ: taskId={}, status={}", taskId, task.getStatus()); |
| | | } |
| | | |
| | | @Override |
| | | public void notifyStepUpdate(String taskId, TaskStepDetail step) { |
| | | if (taskId == null || step == null) { |
| | | return; |
| | | } |
| | | |
| | | Map<String, Object> data = createStepData(step); |
| | | |
| | | // åéç»æå®ä»»å¡çè¿æ¥ |
| | | sendToConnections(taskId, "stepUpdate", data); |
| | | |
| | | // åéç»ææä»»å¡çè¿æ¥ |
| | | Map<String, Object> allTaskData = new HashMap<>(); |
| | | allTaskData.put("taskId", taskId); |
| | | allTaskData.put("step", data); |
| | | sendToAllTaskConnections("stepUpdate", allTaskData); |
| | | |
| | | log.debug("æ¨éæ¥éª¤æ´æ°: taskId={}, stepOrder={}, status={}", |
| | | taskId, step.getStepOrder(), step.getStatus()); |
| | | } |
| | | |
| | | @Override |
| | | public void notifyStepsUpdate(String taskId, List<TaskStepDetail> steps) { |
| | | if (taskId == null || steps == null) { |
| | | return; |
| | | } |
| | | |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("taskId", taskId); |
| | | data.put("steps", steps); |
| | | data.put("stepCount", steps.size()); |
| | | |
| | | // åéç»æå®ä»»å¡çè¿æ¥ |
| | | sendToConnections(taskId, "stepsUpdate", data); |
| | | |
| | | // åéç»ææä»»å¡çè¿æ¥ |
| | | sendToAllTaskConnections("stepsUpdate", data); |
| | | |
| | | log.debug("æ¨éæ¥éª¤åè¡¨æ´æ°: taskId={}, stepCount={}", taskId, steps.size()); |
| | | } |
| | | |
| | | @Override |
| | | public void closeConnections(String taskId) { |
| | | if (taskId == null) { |
| | | return; |
| | | } |
| | | |
| | | List<SseEmitter> emitters = connections.remove(taskId); |
| | | if (emitters != null) { |
| | | for (SseEmitter emitter : emitters) { |
| | | try { |
| | | emitter.complete(); |
| | | } catch (Exception e) { |
| | | log.warn("å
³éSSEè¿æ¥å¤±è´¥: taskId={}", taskId, e); |
| | | } |
| | | } |
| | | log.info("å
³éä»»å¡è¿æ¥: taskId={}, è¿æ¥æ°={}", taskId, emitters.size()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void closeAllConnections() { |
| | | // å
³éææä»»å¡è¿æ¥ |
| | | for (Map.Entry<String, List<SseEmitter>> entry : connections.entrySet()) { |
| | | for (SseEmitter emitter : entry.getValue()) { |
| | | try { |
| | | emitter.complete(); |
| | | } catch (Exception e) { |
| | | log.warn("å
³éSSEè¿æ¥å¤±è´¥: taskId={}", entry.getKey(), e); |
| | | } |
| | | } |
| | | } |
| | | connections.clear(); |
| | | |
| | | // å
³éææä»»å¡çå¬è¿æ¥ |
| | | for (SseEmitter emitter : allTaskConnections) { |
| | | try { |
| | | emitter.complete(); |
| | | } catch (Exception e) { |
| | | log.warn("å
³éSSEè¿æ¥å¤±è´¥", e); |
| | | } |
| | | } |
| | | allTaskConnections.clear(); |
| | | |
| | | log.info("å
³éææSSEè¿æ¥"); |
| | | } |
| | | |
| | | /** |
| | | * åéæ¶æ¯å°æå®ä»»å¡çè¿æ¥ |
| | | */ |
| | | private void sendToConnections(String taskId, String eventName, Map<String, Object> data) { |
| | | List<SseEmitter> emitters = connections.get(taskId); |
| | | if (emitters == null || emitters.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | List<SseEmitter> toRemove = new CopyOnWriteArrayList<>(); |
| | | for (SseEmitter emitter : emitters) { |
| | | try { |
| | | emitter.send(SseEmitter.event() |
| | | .name(eventName) |
| | | .data(createMessage("", data))); |
| | | } catch (IOException e) { |
| | | log.warn("åéSSEæ¶æ¯å¤±è´¥: taskId={}, event={}", taskId, eventName, e); |
| | | toRemove.add(emitter); |
| | | } |
| | | } |
| | | |
| | | // ç§»é¤å¤±è´¥çè¿æ¥ |
| | | emitters.removeAll(toRemove); |
| | | } |
| | | |
| | | /** |
| | | * åéæ¶æ¯å°ææä»»å¡çå¬è¿æ¥ |
| | | */ |
| | | private void sendToAllTaskConnections(String eventName, Map<String, Object> data) { |
| | | if (allTaskConnections.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | List<SseEmitter> toRemove = new CopyOnWriteArrayList<>(); |
| | | for (SseEmitter emitter : allTaskConnections) { |
| | | try { |
| | | emitter.send(SseEmitter.event() |
| | | .name(eventName) |
| | | .data(createMessage("", data))); |
| | | } catch (IOException e) { |
| | | log.warn("åéSSEæ¶æ¯å¤±è´¥: event={}", eventName, e); |
| | | toRemove.add(emitter); |
| | | } |
| | | } |
| | | |
| | | // ç§»é¤å¤±è´¥çè¿æ¥ |
| | | allTaskConnections.removeAll(toRemove); |
| | | } |
| | | |
| | | /** |
| | | * ç§»é¤è¿æ¥ |
| | | */ |
| | | private void removeConnection(String taskId, SseEmitter emitter) { |
| | | if (taskId != null && !taskId.isEmpty()) { |
| | | List<SseEmitter> emitters = connections.get(taskId); |
| | | if (emitters != null) { |
| | | emitters.remove(emitter); |
| | | if (emitters.isEmpty()) { |
| | | connections.remove(taskId); |
| | | } |
| | | } |
| | | } else { |
| | | allTaskConnections.remove(emitter); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * è·åè¿æ¥æ° |
| | | */ |
| | | private int getConnectionCount(String taskId) { |
| | | if (taskId != null && !taskId.isEmpty()) { |
| | | List<SseEmitter> emitters = connections.get(taskId); |
| | | return emitters != null ? emitters.size() : 0; |
| | | } |
| | | return allTaskConnections.size(); |
| | | } |
| | | |
| | | /** |
| | | * å建任å¡ç¶ææ°æ® |
| | | */ |
| | | private Map<String, Object> createTaskStatusData(MultiDeviceTask task) { |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("taskId", task.getTaskId() != null ? task.getTaskId() : ""); |
| | | data.put("groupId", task.getGroupId() != null ? task.getGroupId() : ""); |
| | | data.put("status", task.getStatus() != null ? task.getStatus() : ""); |
| | | data.put("currentStep", task.getCurrentStep() != null ? task.getCurrentStep() : 0); |
| | | data.put("totalSteps", task.getTotalSteps() != null ? task.getTotalSteps() : 0); |
| | | data.put("startTime", task.getStartTime() != null ? task.getStartTime().getTime() : 0); |
| | | data.put("endTime", task.getEndTime() != null ? task.getEndTime().getTime() : 0); |
| | | data.put("errorMessage", task.getErrorMessage() != null ? task.getErrorMessage() : ""); |
| | | return data; |
| | | } |
| | | |
| | | /** |
| | | * å建æ¥éª¤æ°æ® |
| | | */ |
| | | private Map<String, Object> createStepData(TaskStepDetail step) { |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("id", step.getId() != null ? step.getId() : 0); |
| | | data.put("stepOrder", step.getStepOrder() != null ? step.getStepOrder() : 0); |
| | | data.put("deviceId", step.getDeviceId() != null ? step.getDeviceId() : ""); |
| | | data.put("stepName", step.getStepName() != null ? step.getStepName() : ""); |
| | | data.put("status", step.getStatus() != null ? step.getStatus() : ""); |
| | | data.put("startTime", step.getStartTime() != null ? step.getStartTime().getTime() : 0); |
| | | data.put("endTime", step.getEndTime() != null ? step.getEndTime().getTime() : 0); |
| | | data.put("durationMs", step.getDurationMs() != null ? step.getDurationMs() : 0); |
| | | data.put("retryCount", step.getRetryCount() != null ? step.getRetryCount() : 0); |
| | | data.put("errorMessage", step.getErrorMessage() != null ? step.getErrorMessage() : ""); |
| | | return data; |
| | | } |
| | | |
| | | /** |
| | | * åå»ºæ¶æ¯å¯¹è±¡ |
| | | */ |
| | | private String createMessage(String message, Map<String, Object> data) { |
| | | try { |
| | | Map<String, Object> result = new java.util.HashMap<>(); |
| | | result.put("timestamp", System.currentTimeMillis()); |
| | | if (message != null && !message.isEmpty()) { |
| | | result.put("message", message); |
| | | } |
| | | if (data != null && !data.isEmpty()) { |
| | | result.putAll(data); |
| | | } |
| | | return objectMapper.writeValueAsString(result); |
| | | } catch (Exception e) { |
| | | log.error("åºååæ¶æ¯å¤±è´¥", e); |
| | | return "{}"; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | connectTimeout: 5000 |
| | | readTimeout: 5000 |
| | | mybatis-plus: |
| | | mapper-locations: classpath*:mapper/*.xml |
| | | mapper-locations: classpath*:mapper/**/*.xml |
| | | # configuration: |
| | | # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl |
| | | |
| New file |
| | |
| | | -- ç»çä¿¡æ¯è¡¨ |
| | | -- ç¨äºåå¨ç»çIDå尺寸çä¿¡æ¯çæ å°å
³ç³» |
| | | |
| | | CREATE TABLE IF NOT EXISTS glass_info ( |
| | | id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主é®ID', |
| | | glass_id VARCHAR(50) NOT NULL UNIQUE COMMENT 'ç»çIDï¼å¯ä¸æ è¯ï¼', |
| | | glass_length INT DEFAULT NULL COMMENT 'ç»çé¿åº¦ï¼mmï¼', |
| | | glass_width INT DEFAULT NULL COMMENT 'ç»ç宽度ï¼mmï¼', |
| | | glass_thickness DECIMAL(5,2) DEFAULT NULL COMMENT 'ç»çå度ï¼mmï¼', |
| | | glass_type VARCHAR(50) DEFAULT NULL COMMENT 'ç»çç±»å', |
| | | manufacturer VARCHAR(100) DEFAULT NULL COMMENT 'ç产åå', |
| | | production_date DATE DEFAULT NULL COMMENT 'çäº§æ¥æ', |
| | | status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT 'ç¶æï¼ACTIVE-æ´»è·, ARCHIVED-已彿¡£', |
| | | description VARCHAR(500) DEFAULT NULL COMMENT 'æè¿°ä¿¡æ¯', |
| | | created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | created_by VARCHAR(50) DEFAULT 'system' COMMENT 'å建人', |
| | | updated_by VARCHAR(50) DEFAULT 'system' COMMENT 'æ´æ°äºº', |
| | | is_deleted TINYINT DEFAULT 0 COMMENT 'æ¯å¦å é¤ï¼0-å¦ï¼1-æ¯', |
| | | |
| | | INDEX idx_glass_id (glass_id), |
| | | INDEX idx_status (status), |
| | | INDEX idx_created_time (created_time) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ç»çä¿¡æ¯è¡¨'; |
| | | |
| | | -- æå
¥æµè¯æ°æ® |
| | | INSERT INTO glass_info (glass_id, glass_length, glass_width, glass_thickness, glass_type, manufacturer, status, description) VALUES |
| | | ('GLS-2024-001', 2000, 1500, 5.0, 'æ®éç»ç', 'ååA', 'ACTIVE', 'æµè¯ç»ç1'), |
| | | ('GLS-2024-002', 1800, 1200, 6.0, 'é¢åç»ç', 'ååB', 'ACTIVE', 'æµè¯ç»ç2'), |
| | | ('GLS-2024-003', 2200, 1600, 5.5, 'æ®éç»ç', 'ååA', 'ACTIVE', 'æµè¯ç»ç3'), |
| | | ('GLS-2024-004', 1900, 1400, 6.5, 'é¢åç»ç', 'ååC', 'ACTIVE', 'æµè¯ç»ç4'), |
| | | ('GLS-2024-005', 2100, 1500, 5.0, 'æ®éç»ç', 'ååB', 'ACTIVE', 'æµè¯ç»ç5'), |
| | | ('GLS-2024-006', 2000, 1600, 6.0, 'é¢åç»ç', 'ååA', 'ACTIVE', 'æµè¯ç»ç6'), |
| | | ('GLS-2024-007', 1850, 1300, 5.5, 'æ®éç»ç', 'ååC', 'ACTIVE', 'æµè¯ç»ç7'), |
| | | ('GLS-2024-008', 1950, 1450, 6.0, 'é¢åç»ç', 'ååB', 'ACTIVE', 'æµè¯ç»ç8'), |
| | | ('GLS-2024-009', 2050, 1550, 5.0, 'æ®éç»ç', 'ååA', 'ACTIVE', 'æµè¯ç»ç9'), |
| | | ('GLS-2024-010', 1750, 1250, 6.5, 'é¢åç»ç', 'ååC', 'ACTIVE', 'æµè¯ç»ç10'), |
| | | ('DEVICE_001-GLS-001', 2000, 1500, 5.0, 'æ®éç»ç', 'æµè¯åå', 'ACTIVE', '设å¤1æµè¯ç»ç1'), |
| | | ('DEVICE_001-GLS-002', 1800, 1200, 6.0, 'é¢åç»ç', 'æµè¯åå', 'ACTIVE', '设å¤1æµè¯ç»ç2'), |
| | | ('DEVICE_001-GLS-003', 2200, 1600, 5.5, 'æ®éç»ç', 'æµè¯åå', 'ACTIVE', '设å¤1æµè¯ç»ç3'), |
| | | ('DEVICE_002-GLS-001', 1900, 1400, 6.5, 'é¢åç»ç', 'æµè¯åå', 'ACTIVE', '设å¤2æµè¯ç»ç1'), |
| | | ('DEVICE_002-GLS-002', 2100, 1500, 5.0, 'æ®éç»ç', 'æµè¯åå', 'ACTIVE', '设å¤2æµè¯ç»ç2'), |
| | | ('DEVICE_003-GLS-001', 2000, 1600, 6.0, 'é¢åç»ç', 'æµè¯åå', 'ACTIVE', '设å¤3æµè¯ç»ç1'); |
| | | |
| New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| | | <mapper namespace="com.mes.device.mapper.DeviceGlassInfoMapper"> |
| | | |
| | | <!-- æ ¹æ®ç»çIDå表æ¹éæ¥è¯¢ç»çä¿¡æ¯ --> |
| | | <select id="selectByGlassIds" resultType="com.mes.device.entity.GlassInfo"> |
| | | SELECT * FROM glass_info |
| | | WHERE glass_id IN |
| | | <foreach collection="glassIds" item="id" open="(" separator="," close=")"> |
| | | #{id} |
| | | </foreach> |
| | | AND is_deleted = 0 |
| | | </select> |
| | | |
| | | </mapper> |
| | | |
| | |
| | | <!-- æ¹éæ·»å 设å¤å°è®¾å¤ç» --> |
| | | <insert id="batchAddDevicesToGroup" parameterType="map"> |
| | | INSERT INTO device_group_relation (group_id, device_id, role, status, priority, connection_order, |
| | | created_at, updated_at, created_by, updated_by) |
| | | created_time, updated_time, created_by, updated_by) |
| | | SELECT |
| | | #{groupId} as group_id, |
| | | device_id, |
| | | #{groupId}, |
| | | t.device_id, |
| | | CASE |
| | | WHEN #{deviceRole} = 'CONTROLLER' THEN 1 |
| | | WHEN #{deviceRole} = 'COLLABORATOR' THEN 2 |
| | | WHEN #{deviceRole} = 'MONITOR' THEN 3 |
| | | ELSE 2 |
| | | END as role, |
| | | 1 as status, |
| | | 5 as priority, |
| | | ROW_NUMBER() OVER (ORDER BY device_id) as connection_order, |
| | | NOW() as created_at, |
| | | NOW() as updated_at, |
| | | 'system' as created_by, |
| | | 'system' as updated_by |
| | | END, |
| | | 1, |
| | | 5, |
| | | (SELECT IFNULL(MAX(connection_order), 0) FROM device_group_relation WHERE group_id = #{groupId} AND is_deleted = 0) + |
| | | t.row_num as connection_order, |
| | | NOW(), |
| | | NOW(), |
| | | 'system', |
| | | 'system' |
| | | FROM ( |
| | | SELECT |
| | | device_id, |
| | | @row_num := @row_num + 1 as row_num |
| | | FROM ( |
| | | <foreach collection="deviceIds" item="deviceId" separator=" UNION ALL "> |
| | | SELECT #{deviceId} as device_id |
| | | </foreach> |
| | | ) d |
| | | CROSS JOIN (SELECT @row_num := 0) r |
| | | ORDER BY device_id |
| | | ) t |
| | | WHERE NOT EXISTS ( |
| | | SELECT 1 FROM device_group_relation |
| | | WHERE group_id = #{groupId} AND device_id = device_id AND is_deleted = 0 |
| | | SELECT 1 FROM device_group_relation dgr |
| | | WHERE dgr.group_id = #{groupId} AND dgr.device_id = t.device_id AND dgr.is_deleted = 0 |
| | | ) |
| | | </insert> |
| | | |
| | | <!-- æ¹éä»è®¾å¤ç»ç§»é¤è®¾å¤ --> |
| | | <update id="batchRemoveDevicesFromGroup" parameterType="map"> |
| | | UPDATE device_group_relation |
| | | SET is_deleted = 1, updated_at = NOW(), updated_by = 'system' |
| | | SET is_deleted = 1, updated_time = NOW(), updated_by = 'system' |
| | | WHERE group_id = #{groupId} |
| | | AND device_id IN |
| | | <foreach collection="deviceIds" item="deviceId" open="(" separator="," close=")"> |
| | |
| | | WHEN #{deviceRole} = 'MONITOR' THEN 3 |
| | | ELSE 2 |
| | | END, |
| | | updated_at = NOW(), |
| | | updated_time = NOW(), |
| | | updated_by = 'system' |
| | | WHERE group_id = #{groupId} |
| | | AND device_id = #{deviceId} |
| | |
| | | <!-- å é¤è®¾å¤ç»å
³è --> |
| | | <delete id="deleteDeviceFromGroup"> |
| | | UPDATE device_group_relation |
| | | SET is_deleted = 1, updated_at = NOW(), updated_by = 'system' |
| | | SET is_deleted = 1, updated_time = NOW(), updated_by = 'system' |
| | | WHERE group_id = #{groupId} |
| | | AND device_id = #{deviceId} |
| | | AND is_deleted = 0 |
| | |
| | | } |
| | | } |
| | | |
| | | // 设å¤ç¶æç®¡çAPI |
| | | export const deviceStatusApi = { |
| | | /** |
| | | * æ´æ°è®¾å¤å¨çº¿ç¶æ |
| | | * @param {Object} data - { deviceId, status } |
| | | */ |
| | | updateDeviceOnlineStatus(data) { |
| | | return request({ |
| | | url: '/api/plcSend/device/status/update', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | }, |
| | | |
| | | /** |
| | | * æ¹éæ´æ°è®¾å¤å¨çº¿ç¶æ |
| | | * @param {Object} data - { deviceIds, status } |
| | | */ |
| | | batchUpdateDeviceOnlineStatus(data) { |
| | | return request({ |
| | | url: '/api/plcSend/device/status/batch-update', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | }, |
| | | |
| | | /** |
| | | * è·åè®¾å¤ææ°ç¶æ |
| | | * @param {Number} deviceId - 设å¤é
ç½®ID |
| | | */ |
| | | getLatestStatus(deviceId) { |
| | | return request({ |
| | | url: `/api/plcSend/device/status/latest/${deviceId}`, |
| | | method: 'get' |
| | | }) |
| | | }, |
| | | |
| | | /** |
| | | * è®°å½è®¾å¤å¿è·³ |
| | | * @param {Object} data - { deviceId, status } |
| | | */ |
| | | recordHeartbeat(data) { |
| | | return request({ |
| | | url: '/api/plcSend/device/status/heartbeat', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // ç»è®¡API |
| | | export const getDeviceStatistics = (data) => { |
| | | return request({ |
| | |
| | | deviceGroupApi, |
| | | devicePlcApi, |
| | | deviceInteractionApi, |
| | | deviceStatusApi, |
| | | getDeviceStatistics, |
| | | getDeviceGroupStatistics |
| | | } |
| | |
| | | component: () => import('../views/plcTest/return.vue'), |
| | | children: [ |
| | | { |
| | | path: '/plcTest/Test', |
| | | name: 'plcTest', |
| | | component: () => import('../views/plcTest/Test.vue') |
| | | }, |
| | | { |
| | | path: '/plcTest/MultiDeviceWorkbench', |
| | | name: 'MultiDeviceWorkbench', |
| | | component: () => import('../views/plcTest/MultiDeviceWorkbench.vue') |
| | |
| | | <el-form :model="searchForm" :inline="true" class="search-form"> |
| | | <el-form-item label="设å¤ç±»å"> |
| | | <el-select v-model="searchForm.deviceType" placeholder="éæ©è®¾å¤ç±»å" clearable> |
| | | <el-option label="ä¸å¤§è½¦" value="ä¸å¤§è½¦" /> |
| | | <el-option label="大çç" value="大çç" /> |
| | | <el-option label="ç»çåå¨" value="ç»çåå¨" /> |
| | | <el-option label="大车设å¤" value="大车设å¤" /> |
| | | <el-option label="大çç笼" value="大çç笼" /> |
| | | <el-option label="å§å¼ç¼å" value="å§å¼ç¼å" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="设å¤ç¶æ"> |
| | |
| | | // å·¥å
·å½æ° |
| | | const getDeviceTypeTag = (type) => { |
| | | const typeMap = { |
| | | 'ä¸å¤§è½¦': 'primary', |
| | | '大çç': 'success', |
| | | 'ç»çåå¨': 'warning' |
| | | '大车设å¤': 'primary', |
| | | '大çç笼': 'success', |
| | | 'å§å¼ç¼å': 'warning' |
| | | } |
| | | return typeMap[type] || 'info' |
| | | } |
| | |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é»è®¤ç»çé¿åº¦(mm)"> |
| | | <el-input-number |
| | | v-model="deviceLogicParams.defaultGlassLength" |
| | | :min="100" |
| | | :max="10000" |
| | | :step="100" |
| | | style="width: 100%;" |
| | | /> |
| | | <span class="form-tip">å½ç»çæªæä¾é¿åº¦æ¶ä½¿ç¨çé»è®¤å¼</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èªå¨ä¸æ"> |
| | | <el-switch v-model="deviceLogicParams.autoFeed" /> |
| | | <span class="form-tip">æ¯å¦èªå¨è§¦åä¸æè¯·æ±</span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æå¤§éè¯æ¬¡æ°"> |
| | | <el-input-number |
| | |
| | | // ä¸å¤§è½¦åæ° |
| | | vehicleCapacity: 6000, |
| | | glassIntervalMs: 1000, |
| | | defaultGlassLength: 2000, |
| | | autoFeed: true, |
| | | maxRetryCount: 5, |
| | | positionMapping: {}, |
| | |
| | | if (deviceType === 'ä¸å¤§è½¦') { |
| | | deviceLogicParams.vehicleCapacity = deviceLogic.vehicleCapacity ?? 6000 |
| | | deviceLogicParams.glassIntervalMs = deviceLogic.glassIntervalMs ?? 1000 |
| | | deviceLogicParams.defaultGlassLength = deviceLogic.defaultGlassLength ?? 2000 |
| | | deviceLogicParams.autoFeed = deviceLogic.autoFeed ?? true |
| | | deviceLogicParams.maxRetryCount = deviceLogic.maxRetryCount ?? 5 |
| | | deviceLogicParams.positionMapping = deviceLogic.positionMapping || {} |
| | |
| | | // é置设å¤é»è¾åæ° |
| | | deviceLogicParams.vehicleCapacity = 6000 |
| | | deviceLogicParams.glassIntervalMs = 1000 |
| | | deviceLogicParams.defaultGlassLength = 2000 |
| | | deviceLogicParams.autoFeed = true |
| | | deviceLogicParams.maxRetryCount = 5 |
| | | deviceLogicParams.positionMapping = {} |
| | |
| | | if (deviceForm.deviceType === 'ä¸å¤§è½¦') { |
| | | deviceLogic.vehicleCapacity = deviceLogicParams.vehicleCapacity |
| | | deviceLogic.glassIntervalMs = deviceLogicParams.glassIntervalMs |
| | | deviceLogic.defaultGlassLength = deviceLogicParams.defaultGlassLength |
| | | deviceLogic.autoFeed = deviceLogicParams.autoFeed |
| | | deviceLogic.maxRetryCount = deviceLogicParams.maxRetryCount |
| | | deviceLogic.positionMapping = deviceLogicParams.positionMapping |
| | |
| | | <template> |
| | | <el-dialog |
| | | :visible="visible" |
| | | v-model="dialogVisible" |
| | | :title="title" |
| | | width="70%" |
| | | :close-on-click-modal="false" |
| | | @close="handleClose" |
| | | @update:visible="(val) => emit('update:visible', val)" |
| | | > |
| | | <el-form |
| | | ref="formRef" |
| | |
| | | |
| | | <el-form-item label="ç»ç±»å" prop="groupType"> |
| | | <el-select v-model="form.groupType" placeholder="éæ©ç»ç±»å"> |
| | | <el-option label="设å¤ç»" value="设å¤ç»" /> |
| | | <el-option label="管çç»" value="管çç»" /> |
| | | <el-option label="çæ§ç»" value="çæ§ç»" /> |
| | | <el-option label="ç»´æ¤ç»" value="ç»´æ¤ç»" /> |
| | | <el-option label="ç产线" value="ç产线" /> |
| | | <el-option label="æµè¯çº¿" value="æµè¯çº¿" /> |
| | | <el-option label="è¾
å©è®¾å¤ç»" value="è¾
å©è®¾å¤ç»" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | |
| | | <el-select v-model="form.groupStatus" placeholder="éæ©ç»ç¶æ"> |
| | | <el-option label="å¯ç¨" value="ENABLED" /> |
| | | <el-option label="ç¦ç¨" value="DISABLED" /> |
| | | <el-option label="ç»´æ¤ä¸" value="MAINTENANCE" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æ§è¡æ¨¡å¼" prop="executionMode"> |
| | | <el-radio-group v-model="form.executionMode"> |
| | | <el-radio label="SERIAL">ä¸²è¡æ§è¡</el-radio> |
| | | <el-radio label="PARALLEL">å¹¶è¡æ§è¡</el-radio> |
| | | </el-radio-group> |
| | | <div class="form-tip">串è¡ï¼æé¡ºåºä¾æ¬¡æ§è¡è®¾å¤æä½ï¼å¹¶è¡ï¼åæ¶æ§è¡å¤ä¸ªè®¾å¤æä½</div> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="æå¤§è®¾å¤æ°" prop="maxDeviceCount"> |
| | |
| | | const testing = ref(false) |
| | | const saving = ref(false) |
| | | const validationResult = ref(null) |
| | | |
| | | // 使ç¨è®¡ç®å±æ§æ¥åæ¥ visible prop åå
é¨ dialogVisible |
| | | const dialogVisible = computed({ |
| | | get: () => props.visible, |
| | | set: (val) => emit('update:visible', val) |
| | | }) |
| | | const customParamsText = ref('') |
| | | |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | groupName: '', |
| | | groupCode: '', |
| | | groupType: '设å¤ç»', |
| | | groupType: 'ç产线', |
| | | description: '', |
| | | sortOrder: 0, |
| | | groupStatus: 'ENABLED', |
| | | executionMode: 'SERIAL', // æ§è¡æ¨¡å¼ï¼SERIALä¸²è¡ / PARALLELå¹¶è¡ |
| | | maxDeviceCount: 100, |
| | | heartbeatInterval: 30, |
| | | connectionTimeout: 10, |
| | |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | }, { immediate: true }) |
| | | |
| | | // æ¹æ³å®ä¹ |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | groupName: '', |
| | | groupCode: '', |
| | | groupType: '设å¤ç»', |
| | | groupType: 'ç产线', |
| | | description: '', |
| | | sortOrder: 0, |
| | | groupStatus: 'ENABLED', |
| | | executionMode: 'SERIAL', |
| | | maxDeviceCount: 100, |
| | | heartbeatInterval: 30, |
| | | connectionTimeout: 10, |
| | |
| | | const loadFormData = () => { |
| | | if (!props.data) return |
| | | |
| | | // 转æ¢åç«¯ç¶æåæ®µå°åç«¯æ ¼å¼ |
| | | // å端 status: 0=åç¨, 1=å¯ç¨, 2=ç»´æ¤ä¸ |
| | | // å端 groupStatus: "ENABLED"/"DISABLED"/"MAINTENANCE" |
| | | let groupStatus = 'ENABLED' |
| | | if (props.data.status !== undefined) { |
| | | // ä¼å
使ç¨å端ç status åæ®µ |
| | | if (props.data.status === 1) { |
| | | groupStatus = 'ENABLED' |
| | | } else if (props.data.status === 2) { |
| | | groupStatus = 'MAINTENANCE' |
| | | } else { |
| | | groupStatus = 'DISABLED' |
| | | } |
| | | } else if (props.data.groupStatus) { |
| | | // å
¼å®¹å端已æç groupStatus åæ®µ |
| | | groupStatus = props.data.groupStatus |
| | | } |
| | | |
| | | Object.assign(form, { |
| | | groupName: props.data.groupName || '', |
| | | groupCode: props.data.groupCode || '', |
| | | groupType: props.data.groupType || '设å¤ç»', |
| | | groupType: props.data.groupType || 'ç产线', |
| | | description: props.data.description || '', |
| | | sortOrder: props.data.sortOrder || 0, |
| | | groupStatus: props.data.groupStatus || 'ENABLED', |
| | | groupStatus: groupStatus, |
| | | maxDeviceCount: props.data.maxDeviceCount || 100, |
| | | heartbeatInterval: props.data.heartbeatInterval || 30, |
| | | connectionTimeout: props.data.connectionTimeout || 10, |
| | |
| | | logLevel: props.data.logLevel || 'INFO', |
| | | enableAutoBackup: props.data.enableAutoBackup || false, |
| | | backupInterval: props.data.backupInterval || 24, |
| | | customParams: props.data.customParams || {} |
| | | customParams: props.data.customParams || props.data.extraConfig ? (typeof props.data.extraConfig === 'string' ? JSON.parse(props.data.extraConfig) : props.data.extraConfig) : {} |
| | | }) |
| | | |
| | | // ä»customParamsæextraConfigä¸è¯»åexecutionMode |
| | | let executionMode = 'SERIAL' // é»è®¤ä¸²è¡ |
| | | if (form.customParams && form.customParams.executionMode) { |
| | | executionMode = form.customParams.executionMode |
| | | } else if (props.data.extraConfig) { |
| | | try { |
| | | const extraConfig = typeof props.data.extraConfig === 'string' ? JSON.parse(props.data.extraConfig) : props.data.extraConfig |
| | | if (extraConfig.executionMode) { |
| | | executionMode = extraConfig.executionMode |
| | | } |
| | | } catch (e) { |
| | | console.warn('è§£æextraConfig失败:', e) |
| | | } |
| | | } |
| | | form.executionMode = executionMode |
| | | |
| | | customParamsText.value = JSON.stringify(form.customParams, null, 2) |
| | | } |
| | |
| | | return |
| | | } |
| | | |
| | | // 转æ¢åç«¯ç¶æåæ®µå°åç«¯æ ¼å¼ |
| | | // å端 groupStatus: "ENABLED"/"DISABLED"/"MAINTENANCE" |
| | | // å端 status: 0=åç¨, 1=å¯ç¨, 2=ç»´æ¤ä¸ |
| | | let status = 1 // é»è®¤å¯ç¨ |
| | | if (form.groupStatus === 'ENABLED') { |
| | | status = 1 |
| | | } else if (form.groupStatus === 'MAINTENANCE') { |
| | | status = 2 |
| | | } else { |
| | | status = 0 |
| | | } |
| | | |
| | | // å°executionModeä¿åå°customParamsä¸ï¼ä»¥ä¾¿å端ä»extraConfigä¸è¯»å |
| | | const customParams = { |
| | | ...form.customParams, |
| | | executionMode: form.executionMode |
| | | } |
| | | |
| | | const config = { |
| | | ...form, |
| | | customParams: form.customParams |
| | | status: status, // å端éè¦ç status åæ®µ |
| | | customParams: customParams, |
| | | extraConfig: JSON.stringify(customParams) // å端使ç¨extraConfigåæ®µ |
| | | } |
| | | // ç§»é¤å端ä¸ç¨ç groupStatus å executionMode åæ®µï¼é¿å
åç«¯æ··æ· |
| | | delete config.groupStatus |
| | | delete config.executionMode |
| | | |
| | | const response = isEdit.value |
| | | ? await deviceGroupApi.update(props.data.id, config) |
| | |
| | | <el-form :model="searchForm" :inline="true" class="search-form"> |
| | | <el-form-item label="ç»ç±»å"> |
| | | <el-select v-model="searchForm.groupType" placeholder="éæ©ç»ç±»å" clearable> |
| | | <el-option label="设å¤ç»" value="设å¤ç»" /> |
| | | <el-option label="管çç»" value="管çç»" /> |
| | | <el-option label="çæ§ç»" value="çæ§ç»" /> |
| | | <el-option label="ç»´æ¤ç»" value="ç»´æ¤ç»" /> |
| | | <el-option label="ç产线" value="ç产线" /> |
| | | <el-option label="æµè¯çº¿" value="æµè¯çº¿" /> |
| | | <el-option label="è¾
å©è®¾å¤ç»" value="è¾
å©è®¾å¤ç»" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç»ç¶æ"> |
| | |
| | | <template #default="scope"> |
| | | <el-tag :type="getGroupTypeTag(scope.row.groupType)"> |
| | | {{ scope.row.groupType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="executionMode" label="æ§è¡æ¨¡å¼" width="110"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getExecutionModeTag(scope.row)"> |
| | | {{ getExecutionModeText(scope.row) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <div class="device-management"> |
| | | <div class="dialog-header"> |
| | | <div class="device-stats"> |
| | | <el-statistic title="æ»è®¾å¤æ°" :value="currentGroup?.deviceCount || 0" /> |
| | | <el-statistic title="å¨çº¿è®¾å¤" :value="currentGroup?.onlineDeviceCount || 0" /> |
| | | <el-statistic title="离线设å¤" :value="(currentGroup?.deviceCount || 0) - (currentGroup?.onlineDeviceCount || 0)" /> |
| | | <el-statistic title="æ»è®¾å¤æ°" :value="groupDeviceList.length" /> |
| | | <el-statistic title="å¨çº¿è®¾å¤" :value="onlineDeviceCount" /> |
| | | <el-statistic title="离线设å¤" :value="offlineDeviceCount" /> |
| | | </div> |
| | | <div class="dialog-buttons"> |
| | | <el-button type="primary" @click="addDevices">æ·»å 设å¤</el-button> |
| | | <el-select |
| | | v-model="selectedDeviceIds" |
| | | multiple |
| | | filterable |
| | | placeholder="éæ©è¦æ·»å ç设å¤" |
| | | style="width: 300px; margin-right: 12px;" |
| | | @change="handleDeviceSelectChange" |
| | | > |
| | | <el-option |
| | | v-for="device in availableDeviceList" |
| | | :key="device.id || device.deviceId" |
| | | :label="`${device.deviceName} (${device.deviceCode})`" |
| | | :value="device.id || device.deviceId" |
| | | /> |
| | | </el-select> |
| | | <el-button type="danger" @click="removeDevices" :disabled="selectedDevicesInGroup.length === 0"> |
| | | ç§»é¤è®¾å¤ |
| | | </el-button> |
| | |
| | | <el-table-column prop="deviceCode" label="设å¤ç¼ç " /> |
| | | <el-table-column prop="deviceType" label="设å¤ç±»å" /> |
| | | <el-table-column prop="plcIp" label="PLC IP" /> |
| | | <el-table-column prop="deviceStatus" label="设å¤ç¶æ"> |
| | | <el-table-column prop="isOnline" label="å¨çº¿ç¶æ" width="120"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getDeviceStatusTag(scope.row.deviceStatus)" size="small"> |
| | | {{ getDeviceStatusText(scope.row.deviceStatus) }} |
| | | <el-tag :type="scope.row.isOnline ? 'success' : 'info'" size="small"> |
| | | {{ scope.row.isOnline ? 'å¨çº¿' : '离线' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" fixed="right"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | v-if="scope.row.isOnline" |
| | | type="warning" |
| | | size="small" |
| | | @click="updateDeviceOnlineStatus(scope.row, 'OFFLINE')" |
| | | :loading="scope.row.statusUpdating" |
| | | > |
| | | 设为离线 |
| | | </el-button> |
| | | <el-button |
| | | v-else |
| | | type="success" |
| | | size="small" |
| | | @click="updateDeviceOnlineStatus(scope.row, 'ONLINE')" |
| | | :loading="scope.row.statusUpdating" |
| | | > |
| | | 设为å¨çº¿ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <el-button @click="deviceDialogVisible = false">å
³é</el-button> |
| | | <el-button @click="handleCloseDeviceDialog">å
³é</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | |
| | | <!-- ç»è®¡è¯¦æ
å¼¹çª --> |
| | | <el-dialog |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ref, reactive, onMounted, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import { Search, ArrowDown } from '@element-plus/icons-vue' |
| | | import { deviceGroupApi, devicePlcApi } from '@/api/device/deviceManagement' |
| | | import { deviceGroupApi, devicePlcApi, deviceConfigApi, deviceStatusApi } from '@/api/device/deviceManagement' |
| | | |
| | | // ååºå¼æ°æ® |
| | | const groupTable = ref(null) |
| | |
| | | // ç»è®¡å¼¹çª |
| | | const statisticsDialogVisible = ref(false) |
| | | |
| | | // æ·»å 设å¤ç¸å
³ |
| | | const availableDeviceList = ref([]) |
| | | const selectedDeviceIds = ref([]) |
| | | |
| | | // 计ç®å±æ§ï¼æ ¹æ®å®é
设å¤å表计ç®å¨çº¿/ç¦»çº¿è®¾å¤æ°é |
| | | const onlineDeviceCount = computed(() => { |
| | | return groupDeviceList.value.filter(device => { |
| | | const status = device.deviceStatus || device.status |
| | | return status === 'ONLINE' || status === 'å¨çº¿' || device.isOnline === true |
| | | }).length |
| | | }) |
| | | |
| | | const offlineDeviceCount = computed(() => { |
| | | return groupDeviceList.value.length - onlineDeviceCount.value |
| | | }) |
| | | |
| | | // äºä»¶å®ä¹ |
| | | const emit = defineEmits(['group-selected', 'refresh-statistics']) |
| | | |
| | |
| | | const response = await deviceGroupApi.getList(params) |
| | | // MyBatis-Plus Page å¯¹è±¡ç»æï¼{ records: [], total: 0 } |
| | | if (response && response.data) { |
| | | groupList.value = response.data.records || response.data.content || response.data.list || [] |
| | | const records = response.data.records || response.data.content || response.data.list || [] |
| | | // 转æ¢åç«¯ç¶æåæ®µå°åç«¯æ ¼å¼ |
| | | groupList.value = records.map(item => { |
| | | // å端å¯è½è¿åç status æ ¼å¼ï¼ |
| | | // 1. æ°åç±»åï¼0=åç¨, 1=å¯ç¨, 2=ç»´æ¤ä¸ |
| | | // 2. å符串类åï¼"å¯ç¨"ã"åç¨"ã"ç»´æ¤ä¸" |
| | | // 3. å符串类åï¼"ENABLED"ã"DISABLED"ã"MAINTENANCE" |
| | | let statusNum = 0 |
| | | let statusStr = item.status |
| | | |
| | | if (typeof statusStr === 'number') { |
| | | statusNum = statusStr |
| | | } else if (typeof statusStr === 'string') { |
| | | // å¤çä¸æç¶æå符串 |
| | | if (statusStr === 'å¯ç¨' || statusStr === 'ENABLED') { |
| | | statusNum = 1 |
| | | statusStr = 'ENABLED' |
| | | } else if (statusStr === 'åç¨' || statusStr === 'DISABLED') { |
| | | statusNum = 0 |
| | | statusStr = 'DISABLED' |
| | | } else if (statusStr === 'ç»´æ¤ä¸' || statusStr === 'MAINTENANCE') { |
| | | statusNum = 2 |
| | | statusStr = 'MAINTENANCE' |
| | | } else { |
| | | // é»è®¤åç¨ |
| | | statusNum = 0 |
| | | statusStr = 'DISABLED' |
| | | } |
| | | } else if (item.groupStatus) { |
| | | // 妿æ groupStatus åæ®µï¼ä½¿ç¨å® |
| | | if (item.groupStatus === 'ENABLED') { |
| | | statusNum = 1 |
| | | statusStr = 'ENABLED' |
| | | } else if (item.groupStatus === 'MAINTENANCE') { |
| | | statusNum = 2 |
| | | statusStr = 'MAINTENANCE' |
| | | } else { |
| | | statusNum = 0 |
| | | statusStr = 'DISABLED' |
| | | } |
| | | } |
| | | |
| | | return { |
| | | ...item, |
| | | status: statusNum, |
| | | groupStatus: statusStr, |
| | | enabled: statusNum === 1 |
| | | } |
| | | }) |
| | | pagination.total = response.data.total || response.data.totalElements || 0 |
| | | } else { |
| | | groupList.value = [] |
| | |
| | | if (row.enabled) { |
| | | await deviceGroupApi.enable(groupId) |
| | | ElMessage.success('设å¤ç»å¯ç¨æå') |
| | | // åæ¥æ´æ° groupStatus |
| | | row.groupStatus = 'ENABLED' |
| | | row.status = 1 |
| | | } else { |
| | | await deviceGroupApi.disable(groupId) |
| | | ElMessage.success('设å¤ç»ç¦ç¨æå') |
| | | // åæ¥æ´æ° groupStatus |
| | | row.groupStatus = 'DISABLED' |
| | | row.status = 0 |
| | | } |
| | | emit('refresh-statistics') |
| | | loadGroupList() // å·æ°å表 |
| | | // ä¸éæ°å è½½å表ï¼ç´æ¥æ´æ°å½åè¡ç¶æ |
| | | } catch (error) { |
| | | console.error('æ´æ°è®¾å¤ç»ç¶æå¤±è´¥:', error) |
| | | row.enabled = !row.enabled // æ¢å¤ç¶æ |
| | | // æ¢å¤ç¶æ |
| | | row.enabled = !row.enabled |
| | | row.groupStatus = row.enabled ? 'ENABLED' : 'DISABLED' |
| | | row.status = row.enabled ? 1 : 0 |
| | | ElMessage.error('æ´æ°è®¾å¤ç»ç¶æå¤±è´¥: ' + (error.response?.data?.message || error.message)) |
| | | } |
| | | } |
| | |
| | | const manageDevices = async (row) => { |
| | | currentGroup.value = row |
| | | deviceDialogVisible.value = true |
| | | selectedDeviceIds.value = [] |
| | | await loadGroupDevices(row.id || row.groupId) |
| | | await loadAvailableDevices() |
| | | } |
| | | |
| | | const loadGroupDevices = async (groupId) => { |
| | |
| | | deviceLoading.value = true |
| | | const response = await deviceGroupApi.getGroupDevices(groupId) |
| | | if (response && response.data) { |
| | | groupDeviceList.value = response.data || [] |
| | | // 转æ¢å端è¿åçæ°æ®æ ¼å¼ï¼å° status 转æ¢ä¸º deviceStatusï¼å¹¶è½¬æ¢ç¶æå¼ |
| | | groupDeviceList.value = (response.data || []).map(device => { |
| | | // ä¼å
ä½¿ç¨ isOnline åæ®µï¼æ¥èª device_status è¡¨çææ°ç¶æï¼ |
| | | let deviceStatus = 'OFFLINE' |
| | | if (device.isOnline !== undefined && device.isOnline !== null) { |
| | | deviceStatus = device.isOnline ? 'ONLINE' : 'OFFLINE' |
| | | } else if (device.status) { |
| | | // å¦ææ²¡æ isOnlineï¼åä½¿ç¨ status åæ®µ |
| | | const statusMap = { |
| | | 'å¨çº¿': 'ONLINE', |
| | | '离线': 'OFFLINE', |
| | | 'ç»´æ¤ä¸': 'MAINTENANCE', |
| | | 'æ
é': 'MAINTENANCE', |
| | | 'ç¦ç¨': 'DISABLED', |
| | | 'ONLINE': 'ONLINE', |
| | | 'OFFLINE': 'OFFLINE', |
| | | 'MAINTENANCE': 'MAINTENANCE', |
| | | 'DISABLED': 'DISABLED' |
| | | } |
| | | // å¦ææ¯æ°åï¼è½¬æ¢ä¸ºå符串 |
| | | const statusStr = typeof device.status === 'number' |
| | | ? (device.status === 1 ? 'ONLINE' : 'OFFLINE') |
| | | : String(device.status) |
| | | deviceStatus = statusMap[statusStr] || 'OFFLINE' |
| | | } |
| | | |
| | | return { |
| | | ...device, |
| | | deviceStatus: deviceStatus, |
| | | // ä¿çåå§å段以便å
¼å®¹ |
| | | status: device.status, |
| | | isOnline: device.isOnline !== undefined ? device.isOnline : (deviceStatus === 'ONLINE'), |
| | | statusUpdating: false // æ·»å æ´æ°ç¶ææ è®° |
| | | } |
| | | }) |
| | | } else { |
| | | groupDeviceList.value = [] |
| | | } |
| | |
| | | selectedDevicesInGroup.value = selection |
| | | } |
| | | |
| | | const addDevices = () => { |
| | | // æ·»å 设å¤å°ç»é»è¾ |
| | | ElMessage.info('æ·»å 设å¤åè½å¼åä¸...') |
| | | const loadAvailableDevices = async () => { |
| | | try { |
| | | // è·åææè®¾å¤å表 |
| | | const response = await deviceConfigApi.getList({ |
| | | page: 1, |
| | | size: 1000 // è·åè¶³å¤å¤çè®¾å¤ |
| | | }) |
| | | |
| | | if (response && response.data) { |
| | | const allDevices = response.data.records || response.data.content || response.data.list || [] |
| | | // è¿æ»¤æå·²ç»å¨è®¾å¤ç»ä¸çè®¾å¤ |
| | | const currentDeviceIds = new Set(groupDeviceList.value.map(d => d.id || d.deviceId)) |
| | | availableDeviceList.value = allDevices |
| | | .filter(device => { |
| | | const deviceId = device.id || device.deviceId |
| | | return !currentDeviceIds.has(deviceId) |
| | | }) |
| | | .map(device => { |
| | | // 转æ¢è®¾å¤ç¶æ |
| | | let deviceStatus = 'OFFLINE' |
| | | if (device.deviceStatus) { |
| | | deviceStatus = device.deviceStatus |
| | | } else if (device.status) { |
| | | const statusMap = { |
| | | 'å¨çº¿': 'ONLINE', |
| | | '离线': 'OFFLINE', |
| | | 'ç»´æ¤ä¸': 'MAINTENANCE', |
| | | 'æ
é': 'MAINTENANCE', |
| | | 'ç¦ç¨': 'DISABLED' |
| | | } |
| | | deviceStatus = statusMap[device.status] || 'OFFLINE' |
| | | } |
| | | |
| | | return { |
| | | ...device, |
| | | deviceStatus: deviceStatus |
| | | } |
| | | }) |
| | | } else { |
| | | availableDeviceList.value = [] |
| | | } |
| | | } catch (error) { |
| | | console.error('å è½½å¯ç¨è®¾å¤å¤±è´¥:', error) |
| | | ElMessage.error('å è½½å¯ç¨è®¾å¤å¤±è´¥: ' + (error.response?.data?.message || error.message)) |
| | | availableDeviceList.value = [] |
| | | } |
| | | } |
| | | |
| | | const handleDeviceSelectChange = async (deviceIds) => { |
| | | if (!deviceIds || deviceIds.length === 0) { |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const groupId = currentGroup.value.id || currentGroup.value.groupId |
| | | |
| | | await deviceGroupApi.batchAddDevicesToGroup({ |
| | | groupId: groupId, |
| | | deviceIds: deviceIds |
| | | }) |
| | | |
| | | ElMessage.success(`æåæ·»å ${deviceIds.length} 个设å¤å°è®¾å¤ç»`) |
| | | // æ¸
ç©ºéæ© |
| | | selectedDeviceIds.value = [] |
| | | // å·æ°è®¾å¤å表åå¯ç¨è®¾å¤å表 |
| | | await loadGroupDevices(groupId) |
| | | await loadAvailableDevices() |
| | | emit('refresh-statistics') |
| | | } catch (error) { |
| | | console.error('æ·»å 设å¤å¤±è´¥:', error) |
| | | ElMessage.error('æ·»å 设å¤å¤±è´¥: ' + (error.response?.data?.message || error.message)) |
| | | // æ¸
ç©ºéæ©ä»¥ä¾¿éè¯ |
| | | selectedDeviceIds.value = [] |
| | | } |
| | | } |
| | | |
| | | const removeDevices = async () => { |
| | |
| | | return |
| | | } |
| | | |
| | | await ElMessageBox.confirm( |
| | | `ç¡®å®è¦ä»è®¾å¤ç»ä¸ç§»é¤éä¸ç ${selectedDevicesInGroup.value.length} 个设å¤åï¼`, |
| | | 'ç§»é¤è®¾å¤ç¡®è®¤' |
| | | ) |
| | | |
| | | const deviceIds = selectedDevicesInGroup.value.map(item => item.id || item.deviceId) |
| | | // æ¹éç§»é¤è®¾å¤ |
| | | await deviceGroupApi.batchRemoveDevicesFromGroup({ |
| | |
| | | deviceIds |
| | | }) |
| | | ElMessage.success(`æåç§»é¤ ${deviceIds.length} 个设å¤`) |
| | | loadGroupDevices(currentGroup.value.id || currentGroup.value.groupId) |
| | | // æ¸
ç©ºéæ© |
| | | selectedDevicesInGroup.value = [] |
| | | const groupId = currentGroup.value.id || currentGroup.value.groupId |
| | | await loadGroupDevices(groupId) |
| | | await loadAvailableDevices() |
| | | emit('refresh-statistics') |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | console.error('ç§»é¤è®¾å¤å¤±è´¥:', error) |
| | | ElMessage.error('ç§»é¤è®¾å¤å¤±è´¥') |
| | | } |
| | | ElMessage.error('ç§»é¤è®¾å¤å¤±è´¥: ' + (error.response?.data?.message || error.message)) |
| | | } |
| | | } |
| | | |
| | | // æ´æ°è®¾å¤å¨çº¿ç¶æ |
| | | const updateDeviceOnlineStatus = async (device, status) => { |
| | | try { |
| | | // è®¾ç½®æ´æ°ä¸ç¶æ |
| | | device.statusUpdating = true |
| | | |
| | | const deviceId = device.id || device.deviceId |
| | | if (!deviceId) { |
| | | ElMessage.warning('设å¤IDä¸åå¨') |
| | | return |
| | | } |
| | | |
| | | await deviceStatusApi.updateDeviceOnlineStatus({ |
| | | deviceId: deviceId, |
| | | status: status |
| | | }) |
| | | |
| | | // æ´æ°æ¬å°ç¶æ |
| | | device.isOnline = status === 'ONLINE' |
| | | // åæ¶æ´æ° deviceStatus åæ®µä»¥ä¿æä¸è´æ§ |
| | | if (status === 'ONLINE') { |
| | | device.deviceStatus = 'ONLINE' |
| | | } else if (status === 'OFFLINE') { |
| | | device.deviceStatus = 'OFFLINE' |
| | | } |
| | | |
| | | ElMessage.success(`设å¤ç¶æå·²æ´æ°ä¸ºï¼${status === 'ONLINE' ? 'å¨çº¿' : '离线'}`) |
| | | |
| | | // å·æ°è®¾å¤å表以è·åææ°ç¶æ |
| | | const groupId = currentGroup.value.id || currentGroup.value.groupId |
| | | await loadGroupDevices(groupId) |
| | | |
| | | // å·æ°è®¾å¤ç»åè¡¨ä»¥æ´æ°å¨çº¿è®¾å¤æ°éç»è®¡ |
| | | await loadGroupList() |
| | | |
| | | // æ´æ°å½å设å¤ç»çå¨çº¿è®¾å¤æ°éï¼å¦æå½å设å¤ç»å¨å表ä¸ï¼ |
| | | const currentGroupInList = groupList.value.find(g => (g.id || g.groupId) === groupId) |
| | | if (currentGroupInList) { |
| | | // éæ°è®¡ç®å¨çº¿è®¾å¤æ°é |
| | | const onlineCount = groupDeviceList.value.filter(d => d.isOnline === true).length |
| | | currentGroupInList.onlineDeviceCount = onlineCount |
| | | } |
| | | } catch (error) { |
| | | console.error('æ´æ°è®¾å¤å¨çº¿ç¶æå¤±è´¥:', error) |
| | | ElMessage.error('æ´æ°è®¾å¤å¨çº¿ç¶æå¤±è´¥: ' + (error.response?.data?.message || error.message)) |
| | | } finally { |
| | | device.statusUpdating = false |
| | | } |
| | | } |
| | | |
| | | // å
³é设å¤ç®¡çå¼¹çª |
| | | const handleCloseDeviceDialog = async () => { |
| | | deviceDialogVisible.value = false |
| | | // å
³éæ¶å·æ°è®¾å¤ç»å表ï¼ç¡®ä¿å¨çº¿è®¾å¤æ°éæ¯ææ°ç |
| | | await loadGroupList() |
| | | } |
| | | |
| | | const handleCommand = async (command, row) => { |
| | |
| | | // å·¥å
·å½æ° |
| | | const getGroupTypeTag = (type) => { |
| | | const typeMap = { |
| | | '设å¤ç»': 'primary', |
| | | '管çç»': 'success', |
| | | 'çæ§ç»': 'warning', |
| | | 'ç»´æ¤ç»': 'info' |
| | | 'ç产线': 'primary', |
| | | 'æµè¯çº¿': 'success', |
| | | 'è¾
å©è®¾å¤ç»': 'warning' |
| | | } |
| | | return typeMap[type] || 'info' |
| | | } |
| | |
| | | const getGroupStatusTag = (status) => { |
| | | const statusMap = { |
| | | 'ENABLED': 'success', |
| | | 'DISABLED': 'info' |
| | | 'DISABLED': 'info', |
| | | 'MAINTENANCE': 'warning' |
| | | } |
| | | return statusMap[status] || 'info' |
| | | } |
| | |
| | | const getGroupStatusText = (status) => { |
| | | const statusMap = { |
| | | 'ENABLED': 'å¯ç¨', |
| | | 'DISABLED': 'ç¦ç¨' |
| | | 'DISABLED': 'ç¦ç¨', |
| | | 'MAINTENANCE': 'ç»´æ¤ä¸' |
| | | } |
| | | return statusMap[status] || status |
| | | } |
| | | |
| | | // è·åæ§è¡æ¨¡å¼æ ç¾ç±»å |
| | | const getExecutionModeTag = (row) => { |
| | | const mode = getExecutionMode(row) |
| | | return mode === 'PARALLEL' ? 'success' : 'primary' |
| | | } |
| | | |
| | | // è·åæ§è¡æ¨¡å¼ææ¬ |
| | | const getExecutionModeText = (row) => { |
| | | const mode = getExecutionMode(row) |
| | | return mode === 'PARALLEL' ? 'å¹¶è¡æ§è¡' : 'ä¸²è¡æ§è¡' |
| | | } |
| | | |
| | | // ä»extraConfigæcustomParamsä¸æåæ§è¡æ¨¡å¼ |
| | | const getExecutionMode = (row) => { |
| | | // ä¼å
ä»extraConfigä¸è¯»å |
| | | if (row.extraConfig) { |
| | | try { |
| | | const extraConfig = typeof row.extraConfig === 'string' ? JSON.parse(row.extraConfig) : row.extraConfig |
| | | if (extraConfig.executionMode) { |
| | | return extraConfig.executionMode.toUpperCase() |
| | | } |
| | | } catch (e) { |
| | | console.warn('è§£æextraConfig失败:', e) |
| | | } |
| | | } |
| | | // ä»customParamsä¸è¯»å |
| | | if (row.customParams && row.customParams.executionMode) { |
| | | return String(row.customParams.executionMode).toUpperCase() |
| | | } |
| | | // 妿æmaxConcurrentDevicesä¸å¤§äº1ï¼é»è®¤å¹¶è¡ |
| | | if (row.maxConcurrentDevices && row.maxConcurrentDevices > 1) { |
| | | return 'PARALLEL' |
| | | } |
| | | // é»è®¤ä¸²è¡ |
| | | return 'SERIAL' |
| | | } |
| | | |
| | | const getDeviceStatusTag = (status) => { |
| | |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .add-device-dialog { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .dialog-search { |
| | | margin-bottom: 16px; |
| | | padding: 16px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 8px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="group-list-panel"> |
| | | <div class="panel-header"> |
| | | <div> |
| | | <div class="header-title"> |
| | | <h3>设å¤ç»å表</h3> |
| | | <p>éæ©ä¸ä¸ªè®¾å¤ç»è¿è¡ç¼ææµè¯</p> |
| | | </div> |
| | |
| | | .panel-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | align-items: flex-start; |
| | | gap: 16px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .header-title { |
| | | flex: 0 0 auto; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .panel-header h3 { |
| | | margin: 0; |
| | | font-size: 18px; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .panel-header p { |
| | | margin: 2px 0 0; |
| | | color: #909399; |
| | | font-size: 13px; |
| | | white-space: nowrap; |
| | | word-break: keep-all; |
| | | } |
| | | |
| | | .actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .search-input { |
| | | width: 240px; |
| | | min-width: 200px; |
| | | } |
| | | |
| | | /* å°å±å¹æ¶ï¼æä½åºåæ¢è¡ */ |
| | | @media (max-width: 768px) { |
| | | .panel-header { |
| | | flex-direction: column; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | .actions { |
| | | width: 100%; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | } |
| | | |
| | | .group-table { |
| | |
| | | {{ row.currentStep || 0 }} / {{ row.totalSteps || 0 }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å¼å§æ¶é´" min-width="160" prop="startTime" /> |
| | | <el-table-column label="ç»ææ¶é´" min-width="160" prop="endTime" /> |
| | | <el-table-column label="å¼å§æ¶é´" min-width="160"> |
| | | <template #default="{ row }"> |
| | | {{ formatDateTime(row.startTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="ç»ææ¶é´" min-width="160"> |
| | | <template #default="{ row }"> |
| | | {{ formatDateTime(row.endTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <el-drawer v-model="drawerVisible" size="40%" title="任塿¥éª¤è¯¦æ
"> |
| | |
| | | <el-timeline-item |
| | | v-for="step in steps" |
| | | :key="step.id" |
| | | :timestamp="step.startTime || '-'" |
| | | :timestamp="formatDateTime(step.startTime) || '-'" |
| | | :type="step.status === 'COMPLETED' ? 'success' : step.status === 'FAILED' ? 'danger' : 'primary'" |
| | | > |
| | | <div class="step-title">{{ step.stepName }}</div> |
| | |
| | | return `${(ms / 1000).toFixed(1)} s` |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '-' |
| | | try { |
| | | const date = new Date(dateTime) |
| | | // æ£æ¥æ¥ææ¯å¦ææ |
| | | if (isNaN(date.getTime())) { |
| | | return dateTime // å¦ææ æ³è§£æï¼è¿ååå§å¼ |
| | | } |
| | | const year = date.getFullYear() |
| | | const month = String(date.getMonth() + 1).padStart(2, '0') |
| | | const day = String(date.getDate()).padStart(2, '0') |
| | | const hours = String(date.getHours()).padStart(2, '0') |
| | | const minutes = String(date.getMinutes()).padStart(2, '0') |
| | | const seconds = String(date.getSeconds()).padStart(2, '0') |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` |
| | | } catch (error) { |
| | | console.warn('æ ¼å¼åæ¶é´å¤±è´¥:', dateTime, error) |
| | | return dateTime |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.groupId, |
| | | () => { |
| | |
| | | <h3>å¤è®¾å¤æµè¯ç¼æ</h3> |
| | | <p v-if="group">å½å设å¤ç»ï¼{{ group.groupName }}ï¼{{ group.deviceCount || '-' }} å°è®¾å¤ï¼</p> |
| | | <p v-else class="warning">请å
å¨å·¦ä¾§éæ©ä¸ä¸ªè®¾å¤ç»</p> |
| | | <p v-if="group && loadDeviceName" class="sub-info">ä¸å¤§è½¦è®¾å¤ï¼{{ loadDeviceName }}</p> |
| | | </div> |
| | | <el-button type="primary" :disabled="!group" :loading="loading" @click="handleSubmit"> |
| | | <el-icon><Promotion /></el-icon> |
| | | å¯å¨æµè¯ |
| | | </el-button> |
| | | <div class="action-buttons"> |
| | | <el-button |
| | | type="danger" |
| | | plain |
| | | :disabled="!group || !loadDeviceId || loadDeviceLoading" |
| | | :loading="clearLoading" |
| | | @click="handleClearPlc" |
| | | > |
| | | <el-icon><Delete /></el-icon> |
| | | æ¸
空PLC |
| | | </el-button> |
| | | <el-button type="primary" :disabled="!group" :loading="loading" @click="handleSubmit"> |
| | | <el-icon><Promotion /></el-icon> |
| | | å¯å¨æµè¯ |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-form :model="form" label-width="120px"> |
| | |
| | | <script setup> |
| | | import { computed, reactive, ref, watch } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { Promotion } from '@element-plus/icons-vue' |
| | | import { Delete, Promotion } from '@element-plus/icons-vue' |
| | | import { multiDeviceTaskApi } from '@/api/device/multiDeviceTask' |
| | | import { deviceGroupApi, deviceInteractionApi } from '@/api/device/deviceManagement' |
| | | |
| | | const props = defineProps({ |
| | | group: { |
| | |
| | | |
| | | const glassIdsInput = ref('') |
| | | const loading = ref(false) |
| | | const clearLoading = ref(false) |
| | | const loadDeviceId = ref(null) |
| | | const loadDeviceName = ref('') |
| | | const loadDeviceLoading = ref(false) |
| | | |
| | | watch( |
| | | () => props.group, |
| | | () => { |
| | | glassIdsInput.value = '' |
| | | fetchLoadDevice() |
| | | } |
| | | ) |
| | | |
| | |
| | | .map((item) => item.trim()) |
| | | .filter((item) => item.length > 0) |
| | | }) |
| | | |
| | | const fetchLoadDevice = async () => { |
| | | loadDeviceId.value = null |
| | | loadDeviceName.value = '' |
| | | if (!props.group) { |
| | | return |
| | | } |
| | | const groupId = props.group.id || props.group.groupId |
| | | if (!groupId) { |
| | | return |
| | | } |
| | | loadDeviceLoading.value = true |
| | | try { |
| | | const response = await deviceGroupApi.getGroupDevices(groupId) |
| | | const rawList = response?.data |
| | | const deviceList = Array.isArray(rawList) |
| | | ? rawList |
| | | : Array.isArray(rawList?.records) |
| | | ? rawList.records |
| | | : Array.isArray(rawList?.data) |
| | | ? rawList.data |
| | | : [] |
| | | const targetDevice = |
| | | deviceList.find((item) => (item.deviceType || '').toUpperCase() === 'LOAD_VEHICLE') || |
| | | deviceList[0] |
| | | if (targetDevice && targetDevice.id) { |
| | | loadDeviceId.value = targetDevice.id |
| | | loadDeviceName.value = targetDevice.deviceName || targetDevice.deviceCode || `ID: ${targetDevice.id}` |
| | | } |
| | | } catch (error) { |
| | | console.error('å 载设å¤ä¿¡æ¯å¤±è´¥:', error) |
| | | ElMessage.error(error?.message || 'è·å设å¤ä¿¡æ¯å¤±è´¥') |
| | | } finally { |
| | | loadDeviceLoading.value = false |
| | | } |
| | | } |
| | | |
| | | const handleSubmit = async () => { |
| | | if (!props.group) { |
| | |
| | | ElMessage.error(error?.message || 'ä»»å¡å¯å¨å¤±è´¥') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | const handleClearPlc = async () => { |
| | | if (!props.group) { |
| | | ElMessage.warning('请å
éæ©è®¾å¤ç»') |
| | | return |
| | | } |
| | | if (!loadDeviceId.value) { |
| | | ElMessage.warning('æªæ¾å°ä¸å¤§è½¦è®¾å¤ï¼æ æ³æ¸
空PLC') |
| | | return |
| | | } |
| | | try { |
| | | clearLoading.value = true |
| | | const response = await deviceInteractionApi.executeOperation({ |
| | | deviceId: loadDeviceId.value, |
| | | operation: 'clearGlass', |
| | | params: { |
| | | positionCode: form.positionCode || null |
| | | } |
| | | }) |
| | | if (response?.code !== 200) { |
| | | throw new Error(response?.message || 'PLCæ¸
空失败') |
| | | } |
| | | const result = response?.data |
| | | if (result?.success) { |
| | | ElMessage.success(result?.message || 'PLCå·²æ¸
空') |
| | | glassIdsInput.value = '' |
| | | } else { |
| | | throw new Error(result?.message || 'PLCæ¸
空失败') |
| | | } |
| | | } catch (error) { |
| | | console.error('æ¸
空PLC失败:', error) |
| | | ElMessage.error(error?.message || 'PLCæ¸
空失败') |
| | | } finally { |
| | | clearLoading.value = false |
| | | } |
| | | } |
| | | </script> |
| | |
| | | .panel-header .warning { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .panel-header .sub-info { |
| | | margin-top: 4px; |
| | | color: #606266; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | gap: 12px; |
| | | align-items: center; |
| | | } |
| | | </style> |
| | | |