opt:西门子优化

This commit is contained in:
2026-03-10 13:37:58 +08:00
parent f76e7b1142
commit ff64d2d48c
19 changed files with 1136 additions and 23 deletions

View File

@@ -0,0 +1,38 @@
package org.nl.acs.agv_usage.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("acs_agv_daily_statistics")
public class AcsAgvDailyStatistics implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
private String device_code;
private String device_name;
private Date work_date;
private Long total_work_duration;
private Long total_idle_duration;
private BigDecimal usage_rate;
private Integer task_count;
private Date create_time;
private Date update_time;
}

View File

@@ -0,0 +1,43 @@
package org.nl.acs.agv_usage.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
@TableName("acs_agv_usage_record")
public class AcsAgvUsageRecord implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
private String device_code;
private String device_name;
private String task_code;
private String instruction_code;
private Date start_time;
private Date end_time;
private Long work_duration;
private Date work_date;
private String status;
private Date create_time;
private Date update_time;
private String is_active;
}

View File

@@ -0,0 +1,19 @@
package org.nl.acs.agv_usage.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics;
import java.util.Date;
import java.util.List;
@Mapper
public interface AcsAgvDailyStatisticsMapper extends BaseMapper<AcsAgvDailyStatistics> {
AcsAgvDailyStatistics selectByDeviceCodeAndDate(@Param("deviceCode") String deviceCode, @Param("workDate") Date workDate);
List<AcsAgvDailyStatistics> selectByDateRange(@Param("startDate") Date startDate, @Param("endDate") Date endDate);
List<AcsAgvDailyStatistics> selectByDeviceCodeAndDateRange(@Param("deviceCode") String deviceCode, @Param("startDate") Date startDate, @Param("endDate") Date endDate);
}

View File

@@ -0,0 +1,37 @@
<?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="org.nl.acs.agv_usage.mapper.AcsAgvDailyStatisticsMapper">
<resultMap id="BaseResultMap" type="org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics">
<id column="id" property="id"/>
<result column="device_code" property="device_code"/>
<result column="device_name" property="device_name"/>
<result column="work_date" property="work_date"/>
<result column="total_work_duration" property="total_work_duration"/>
<result column="total_idle_duration" property="total_idle_duration"/>
<result column="usage_rate" property="usage_rate"/>
<result column="task_count" property="task_count"/>
<result column="create_time" property="create_time"/>
<result column="update_time" property="update_time"/>
</resultMap>
<select id="selectByDeviceCodeAndDate" resultMap="BaseResultMap">
SELECT * FROM acs_agv_daily_statistics
WHERE device_code = #{deviceCode}
AND work_date = #{workDate}
</select>
<select id="selectByDateRange" resultMap="BaseResultMap">
SELECT * FROM acs_agv_daily_statistics
WHERE work_date BETWEEN #{startDate} AND #{endDate}
ORDER BY device_code, work_date
</select>
<select id="selectByDeviceCodeAndDateRange" resultMap="BaseResultMap">
SELECT * FROM acs_agv_daily_statistics
WHERE device_code = #{deviceCode}
AND work_date BETWEEN #{startDate} AND #{endDate}
ORDER BY work_date
</select>
</mapper>

View File

@@ -0,0 +1,17 @@
package org.nl.acs.agv_usage.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.nl.acs.agv_usage.domain.AcsAgvUsageRecord;
import java.util.Date;
import java.util.List;
@Mapper
public interface AcsAgvUsageRecordMapper extends BaseMapper<AcsAgvUsageRecord> {
AcsAgvUsageRecord selectActiveByDeviceCode(@Param("deviceCode") String deviceCode);
List<AcsAgvUsageRecord> selectByDeviceCodeAndDate(@Param("deviceCode") String deviceCode, @Param("workDate") Date workDate);
}

View File

@@ -0,0 +1,38 @@
<?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="org.nl.acs.agv_usage.mapper.AcsAgvUsageRecordMapper">
<resultMap id="BaseResultMap" type="org.nl.acs.agv_usage.domain.AcsAgvUsageRecord">
<id column="id" property="id"/>
<result column="device_code" property="device_code"/>
<result column="device_name" property="device_name"/>
<result column="task_code" property="task_code"/>
<result column="instruction_code" property="instruction_code"/>
<result column="start_time" property="start_time"/>
<result column="end_time" property="end_time"/>
<result column="work_duration" property="work_duration"/>
<result column="work_date" property="work_date"/>
<result column="status" property="status"/>
<result column="create_time" property="create_time"/>
<result column="update_time" property="update_time"/>
<result column="is_active" property="is_active"/>
</resultMap>
<select id="selectActiveByDeviceCode" resultMap="BaseResultMap">
SELECT * FROM acs_agv_usage_record
WHERE device_code = #{deviceCode}
AND status = '0'
AND is_active = '1'
ORDER BY start_time DESC
LIMIT 1
</select>
<select id="selectByDeviceCodeAndDate" resultMap="BaseResultMap">
SELECT * FROM acs_agv_usage_record
WHERE device_code = #{deviceCode}
AND work_date = #{workDate}
AND is_active = '1'
ORDER BY start_time ASC
</select>
</mapper>

View File

@@ -0,0 +1,86 @@
package org.nl.acs.agv_usage.rest;
import cn.dev33.satoken.annotation.SaIgnore;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics;
import org.nl.acs.agv_usage.service.AcsAgvUsageService;
import org.nl.common.logging.annotation.Log;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequiredArgsConstructor
@Api(tags = "AGV使用率统计接口")
@RequestMapping("/api/agv_usage")
@SaIgnore
public class AgvUsageController {
private final AcsAgvUsageService agvUsageService;
@GetMapping("/statistics")
@Log("获取AGV使用率统计")
@ApiOperation("获取AGV使用率统计")
public ResponseEntity<Object> getStatistics(
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
List<Map<String, Object>> result = agvUsageService.getAgvUsageRate(startDate, endDate);
return new ResponseEntity<>(result, HttpStatus.OK);
}
@GetMapping("/statistics/detail")
@Log("获取AGV详细统计")
@ApiOperation("获取AGV详细统计")
public ResponseEntity<Object> getStatisticsDetail(
@RequestParam(required = false) String deviceCode,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) {
List<AcsAgvDailyStatistics> result;
if (deviceCode != null && !deviceCode.isEmpty()) {
result = agvUsageService.getStatisticsByDeviceAndDateRange(deviceCode, startDate, endDate);
} else {
result = agvUsageService.getStatisticsByDateRange(startDate, endDate);
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
@GetMapping("/realtime")
@Log("获取AGV实时状态")
@ApiOperation("获取AGV实时状态")
public ResponseEntity<Object> getRealtimeStatus() {
Map<String, Object> result = agvUsageService.getAgvRealtimeStatus();
return new ResponseEntity<>(result, HttpStatus.OK);
}
@PostMapping("/start")
@Log("AGV开始工作")
@ApiOperation("AGV开始工作")
public ResponseEntity<Object> startWork(
@RequestParam String deviceCode,
@RequestParam(required = false) String deviceName,
@RequestParam(required = false) String taskCode,
@RequestParam(required = false) String instructionCode) {
agvUsageService.startWork(deviceCode, deviceName, taskCode, instructionCode);
return new ResponseEntity<>("success", HttpStatus.OK);
}
@PostMapping("/end")
@Log("AGV结束工作")
@ApiOperation("AGV结束工作")
public ResponseEntity<Object> endWork(
@RequestParam String deviceCode,
@RequestParam(required = false) String taskCode,
@RequestParam(required = false) String instructionCode) {
agvUsageService.endWork(deviceCode, taskCode, instructionCode);
return new ResponseEntity<>("success", HttpStatus.OK);
}
}

View File

@@ -0,0 +1,25 @@
package org.nl.acs.agv_usage.service;
import org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics;
import org.nl.acs.agv_usage.domain.AcsAgvUsageRecord;
import java.util.Date;
import java.util.List;
import java.util.Map;
public interface AcsAgvUsageService {
void startWork(String deviceCode, String deviceName, String taskCode, String instructionCode);
void endWork(String deviceCode, String taskCode, String instructionCode);
void updateDailyStatistics(String deviceCode, Date workDate);
List<AcsAgvDailyStatistics> getStatisticsByDateRange(Date startDate, Date endDate);
List<AcsAgvDailyStatistics> getStatisticsByDeviceAndDateRange(String deviceCode, Date startDate, Date endDate);
List<Map<String, Object>> getAgvUsageRate(Date startDate, Date endDate);
Map<String, Object> getAgvRealtimeStatus();
}

View File

@@ -0,0 +1,221 @@
package org.nl.acs.agv_usage.service.impl;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.nl.acs.agv_usage.domain.AcsAgvDailyStatistics;
import org.nl.acs.agv_usage.domain.AcsAgvUsageRecord;
import org.nl.acs.agv_usage.mapper.AcsAgvDailyStatisticsMapper;
import org.nl.acs.agv_usage.mapper.AcsAgvUsageRecordMapper;
import org.nl.acs.agv_usage.service.AcsAgvUsageService;
import org.nl.acs.device.domain.Device;
import org.nl.acs.device.service.DeviceService;
import org.nl.acs.opc.DeviceAppService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
@Slf4j
@Service
@RequiredArgsConstructor
public class AcsAgvUsageServiceImpl implements AcsAgvUsageService {
private final AcsAgvUsageRecordMapper usageRecordMapper;
private final AcsAgvDailyStatisticsMapper dailyStatisticsMapper;
private final DeviceAppService deviceAppService;
private final DeviceService deviceService;
private static final long DAY_SECONDS = 24 * 60 * 60L;
private static final long WORK_DAY_SECONDS = 8 * 60 * 60L;
@Override
@Transactional(rollbackFor = Exception.class)
public void startWork(String deviceCode, String deviceName, String taskCode, String instructionCode) {
AcsAgvUsageRecord existingRecord = usageRecordMapper.selectActiveByDeviceCode(deviceCode);
if (existingRecord != null) {
log.warn("AGV {} 已有工作中的记录,先结束上一条记录", deviceCode);
endWork(deviceCode, existingRecord.getTask_code(), existingRecord.getInstruction_code());
}
AcsAgvUsageRecord record = new AcsAgvUsageRecord();
record.setId(IdUtil.simpleUUID());
record.setDevice_code(deviceCode);
record.setDevice_name(deviceName);
record.setTask_code(taskCode);
record.setInstruction_code(instructionCode);
record.setStart_time(new Date());
record.setWork_date(new Date());
record.setStatus("0");
record.setWork_duration(0L);
record.setIs_active("1");
record.setCreate_time(new Date());
record.setUpdate_time(new Date());
usageRecordMapper.insert(record);
log.info("AGV {} 开始工作,任务号: {}, 指令号: {}", deviceCode, taskCode, instructionCode);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void endWork(String deviceCode, String taskCode, String instructionCode) {
AcsAgvUsageRecord record = usageRecordMapper.selectActiveByDeviceCode(deviceCode);
if (record == null) {
log.warn("AGV {} 没有找到工作中的记录", deviceCode);
return;
}
Date endTime = new Date();
long workDuration = (endTime.getTime() - record.getStart_time().getTime()) / 1000;
record.setEnd_time(endTime);
record.setWork_duration(workDuration);
record.setStatus("1");
record.setUpdate_time(new Date());
usageRecordMapper.updateById(record);
updateDailyStatistics(deviceCode, record.getWork_date());
log.info("AGV {} 结束工作,任务号: {}, 工作时长: {}秒", deviceCode, taskCode, workDuration);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateDailyStatistics(String deviceCode, Date workDate) {
List<AcsAgvUsageRecord> records = usageRecordMapper.selectByDeviceCodeAndDate(deviceCode, workDate);
long totalWorkDuration = records.stream()
.filter(r -> "1".equals(r.getStatus()))
.mapToLong(AcsAgvUsageRecord::getWork_duration)
.sum();
int taskCount = (int) records.stream()
.filter(r -> "1".equals(r.getStatus()))
.count();
String deviceName = null;
Device device = deviceAppService.findDeviceByCode(deviceCode);
if (device != null) {
deviceName = device.getDevice_name();
}
AcsAgvDailyStatistics existingStats = dailyStatisticsMapper.selectByDeviceCodeAndDate(deviceCode, workDate);
long totalIdleDuration = DAY_SECONDS - totalWorkDuration;
if (totalIdleDuration < 0) {
totalIdleDuration = 0;
}
BigDecimal usageRate = BigDecimal.valueOf(totalWorkDuration)
.divide(BigDecimal.valueOf(DAY_SECONDS), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.setScale(2, RoundingMode.HALF_UP);
if (existingStats != null) {
existingStats.setTotal_work_duration(totalWorkDuration);
existingStats.setTotal_idle_duration(totalIdleDuration);
existingStats.setUsage_rate(usageRate);
existingStats.setTask_count(taskCount);
existingStats.setUpdate_time(new Date());
dailyStatisticsMapper.updateById(existingStats);
} else {
AcsAgvDailyStatistics newStats = new AcsAgvDailyStatistics();
newStats.setId(IdUtil.simpleUUID());
newStats.setDevice_code(deviceCode);
newStats.setDevice_name(deviceName);
newStats.setWork_date(workDate);
newStats.setTotal_work_duration(totalWorkDuration);
newStats.setTotal_idle_duration(totalIdleDuration);
newStats.setUsage_rate(usageRate);
newStats.setTask_count(taskCount);
newStats.setCreate_time(new Date());
newStats.setUpdate_time(new Date());
dailyStatisticsMapper.insert(newStats);
}
}
@Override
public List<AcsAgvDailyStatistics> getStatisticsByDateRange(Date startDate, Date endDate) {
return dailyStatisticsMapper.selectByDateRange(startDate, endDate);
}
@Override
public List<AcsAgvDailyStatistics> getStatisticsByDeviceAndDateRange(String deviceCode, Date startDate, Date endDate) {
return dailyStatisticsMapper.selectByDeviceCodeAndDateRange(deviceCode, startDate, endDate);
}
@Override
public List<Map<String, Object>> getAgvUsageRate(Date startDate, Date endDate) {
List<AcsAgvDailyStatistics> statistics = getStatisticsByDateRange(startDate, endDate);
Map<String, Map<String, Object>> deviceStatsMap = new LinkedHashMap<>();
for (AcsAgvDailyStatistics stat : statistics) {
String deviceCode = stat.getDevice_code();
if (!deviceStatsMap.containsKey(deviceCode)) {
Map<String, Object> deviceData = new HashMap<>();
deviceData.put("deviceCode", deviceCode);
deviceData.put("deviceName", stat.getDevice_name());
deviceData.put("totalWorkDuration", 0L);
deviceData.put("totalTaskCount", 0);
deviceData.put("avgUsageRate", BigDecimal.ZERO);
deviceData.put("dayCount", 0);
deviceData.put("usageRateSum", BigDecimal.ZERO);
deviceStatsMap.put(deviceCode, deviceData);
}
Map<String, Object> deviceData = deviceStatsMap.get(deviceCode);
deviceData.put("totalWorkDuration", (Long) deviceData.get("totalWorkDuration") + stat.getTotal_work_duration());
deviceData.put("totalTaskCount", (Integer) deviceData.get("totalTaskCount") + stat.getTask_count());
deviceData.put("dayCount", (Integer) deviceData.get("dayCount") + 1);
deviceData.put("usageRateSum", ((BigDecimal) deviceData.get("usageRateSum")).add(stat.getUsage_rate()));
}
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> deviceData : deviceStatsMap.values()) {
int dayCount = (Integer) deviceData.get("dayCount");
if (dayCount > 0) {
BigDecimal avgUsageRate = ((BigDecimal) deviceData.get("usageRateSum"))
.divide(BigDecimal.valueOf(dayCount), 2, RoundingMode.HALF_UP);
deviceData.put("avgUsageRate", avgUsageRate);
}
deviceData.remove("usageRateSum");
deviceData.remove("dayCount");
result.add(deviceData);
}
return result;
}
@Override
public Map<String, Object> getAgvRealtimeStatus() {
Map<String, Object> result = new HashMap<>();
LambdaQueryWrapper<AcsAgvUsageRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(AcsAgvUsageRecord::getStatus, "0")
.eq(AcsAgvUsageRecord::getIs_active, "1");
List<AcsAgvUsageRecord> workingRecords = usageRecordMapper.selectList(wrapper);
List<Map<String, Object>> workingAgvList = new ArrayList<>();
for (AcsAgvUsageRecord record : workingRecords) {
Map<String, Object> agvStatus = new HashMap<>();
agvStatus.put("deviceCode", record.getDevice_code());
agvStatus.put("deviceName", record.getDevice_name());
agvStatus.put("taskCode", record.getTask_code());
agvStatus.put("startTime", record.getStart_time());
agvStatus.put("currentDuration", (System.currentTimeMillis() - record.getStart_time().getTime()) / 1000);
workingAgvList.add(agvStatus);
}
result.put("workingAgvList", workingAgvList);
result.put("workingCount", workingAgvList.size());
return result;
}
}

View File

@@ -45,6 +45,7 @@ import org.nl.system.service.dict.dao.Dict;
import org.nl.system.service.param.ISysParamService;
import org.nl.config.SpringContextHolder;
import org.nl.system.service.param.impl.SysParamServiceImpl;
import org.nl.acs.agv_usage.service.AcsAgvUsageService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
@@ -70,6 +71,7 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
DeviceAppService deviceAppService = SpringContextHolder.getBean(DeviceAppService.class);
DeviceService deviceService = SpringContextHolder.getBean(DeviceService.class);
ISysDictService dictService = SpringContextHolder.getBean(ISysDictService.class);
AcsAgvUsageService agvUsageService = SpringContextHolder.getBean(AcsAgvUsageService.class);
int agvaddr = 0;
int agvaddr_copy = 0;
@@ -155,6 +157,19 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
transportOrder = inst.getTask_code();
logServer.deviceExecuteLog(this.device_code, "", "", "agvphase" + "反馈:" + data);
// AGV使用率统计 - 开始工作
try {
String agvDeviceCode = "agv" + carno;
String agvDeviceName = this.device_code;
Device agvDevice = deviceAppService.findDeviceByCode(agvDeviceCode);
if (agvDevice != null) {
agvDeviceName = agvDevice.getDevice_name();
}
agvUsageService.startWork(agvDeviceCode, agvDeviceName, inst.getTask_code(), inst.getInstruction_code());
} catch (Exception e) {
log.error("AGV使用率统计-开始工作失败: {}", e.getMessage());
}
//到达取货点
//(需要WCS反馈)
} else if (phase == 0x03) {
@@ -463,6 +478,17 @@ public class AgvNdcOneDeviceDriver extends AbstractDeviceDriver implements Devic
return;
}
transportOrder = "";
// AGV使用率统计 - 结束工作
try {
String agvDeviceCode = "agv" + inst.getCarno();
if (StrUtil.isNotEmpty(agvDeviceCode)) {
agvUsageService.endWork(agvDeviceCode, inst.getTask_code(), inst.getInstruction_code());
}
} catch (Exception e) {
log.error("AGV使用率统计-结束工作失败: {}", e.getMessage());
}
if (device.getDeviceDriver() instanceof PlcToAgvDeviceDriver) {
PlcToAgvDeviceDriver plcToAgvDeviceDriver = (PlcToAgvDeviceDriver) device.getDeviceDriver();
if (device_code.contains("_01")) {