diff --git a/nlsso-server/src/main/java/org/nl/acs/device_driver/agv/ndctwo/AgvNdcTwoDeviceDriver.java b/nlsso-server/src/main/java/org/nl/acs/device_driver/agv/ndctwo/AgvNdcTwoDeviceDriver.java index c8bba00..fae45a1 100644 --- a/nlsso-server/src/main/java/org/nl/acs/device_driver/agv/ndctwo/AgvNdcTwoDeviceDriver.java +++ b/nlsso-server/src/main/java/org/nl/acs/device_driver/agv/ndctwo/AgvNdcTwoDeviceDriver.java @@ -54,6 +54,7 @@ import org.nl.system.service.param.ISysParamService; import org.nl.config.SpringContextHolder; import java.time.LocalTime; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -116,6 +117,15 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic public synchronized void processSocket(int[] arr) throws Exception { device_code = this.getDeviceCode(); + + // 方法入口日志:记录接收到的原始数据 + LuceneLogDto entryLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[processSocket] 开始处理AGV数据, arr长度:" + arr.length + ", device_code:" + device_code) + .build(); + entryLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(entryLog); + byte[] data = null; phase = arr[16] * 256 + arr[17]; // agv任务号 @@ -128,10 +138,26 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic agvaddr = arr[18] * 256 + arr[19]; //车号 int carno = arr[20]; + + // 记录关键参数 + LuceneLogDto paramLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[AGV参数] phase:" + phase + "(0x" + Integer.toHexString(phase) + "), index:" + index + ", ikey:" + ikey + ", agvaddr:" + agvaddr + ", carno:" + carno) + .build(); + paramLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(paramLog); + Instruction inst = null; Device agv_device = null; if (carno != 0) { agv_device = deviceAppService.findDeviceByCode(String.valueOf(carno)); + // AGV设备查询日志 + LuceneLogDto agvDeviceLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[AGV设备查询] carno:" + carno + ", 设备存在:" + (agv_device != null)) + .build(); + agvDeviceLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(agvDeviceLog); } TaskDto task = null; if (ikey != 0) { @@ -140,15 +166,18 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic this.instruction = inst; } if (ObjectUtil.isNotEmpty(inst)) { -// log.info("该指令号未找到对应指令:" + ikey); -// message = "该指令号未找到对应指令:" + ikey; -// logServer.deviceExecuteLog(this.device_code, "", "", "该指令号未找到对应指令:" + ikey); -// return; task = taskService.findByTaskCode(inst.getTask_code()); + // 任务查询日志 + LuceneLogDto taskLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[任务查询] ikey:" + ikey + ", 指令存在, task_code:" + inst.getTask_code() + ", task存在:" + (task != null)) + .build(); + taskLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(taskLog); } else { LuceneLogDto logDto = LuceneLogDto.builder() .device_code(device_code) - .content("指令号为空!") + .content("[指令查询] 未找到指令号:" + ikey) .build(); logDto.setLog_level(4); luceneExecuteLogService.deviceExecuteLog(logDto); @@ -262,15 +291,28 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic luceneExecuteLogService.deviceExecuteLog(logDto3); HttpResponse httpResponse2 = acsToWcsService.feedbackTaskStatusToWcs(param3); if (ObjectUtil.isNotEmpty(httpResponse2)) { - JSONObject param2 = new JSONObject(); - param2.put("device_code", device_code); + String responseBody = httpResponse2.body(); + JSONObject responseJson = JSONObject.parseObject(responseBody); + int responseCode = responseJson.getIntValue("responseCode"); + LuceneLogDto logDto2 = LuceneLogDto.builder() .device_code(device_code) - .content("申请取货返回参数:" + param2) + .content("申请取货返回参数:" + responseBody) .build(); logDto2.setLog_level(4); luceneExecuteLogService.deviceExecuteLog(logDto2); - + + // 判断 responseCode 是否为 0,非 0 则返回 + if (responseCode != 0) { + LuceneLogDto errorLog = LuceneLogDto.builder() + .device_code(device_code) + .content("WCS反馈失败, responseCode:" + responseCode + + ", responseMessage:" + responseJson.getString("responseMessage")) + .build(); + errorLog.setLog_level(2); + luceneExecuteLogService.deviceExecuteLog(errorLog); + return; + } } if (device.getDeviceDriver() instanceof StandardOrdinarySiteDeviceDriver) { @@ -325,27 +367,69 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic //烘箱 //到达取货点 else if (device.getDeviceDriver() instanceof HongXiangStationDeviceDriver) { hongXiangStationDeviceDriver = (HongXiangStationDeviceDriver) device.getDeviceDriver(); + + // 烘箱交互日志:开始处理烘箱取货 + LuceneLogDto ovenLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱交互] 到达取货点, ovenNo:" + hongXiangLocationInfo.getOvenNo() + + ", shutterDoor:" + hongXiangLocationInfo.getShutterDoor() + + ", storageNo:" + hongXiangLocationInfo.getStorageNo()) + .build(); + ovenLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(ovenLog); //判断对应点位是卷帘门是否开门到位 int door_open = (int) hongXiangStationDeviceDriver.getPropertyValue("item_oven" + hongXiangLocationInfo.getOvenNo() + "_door" + hongXiangLocationInfo.getShutterDoor() + "_open"); + + // 记录门状态 + LuceneLogDto doorStatusLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱门状态] oven" + hongXiangLocationInfo.getOvenNo() + + "_door" + hongXiangLocationInfo.getShutterDoor() + "_open = " + door_open) + .build(); + doorStatusLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(doorStatusLog); + if (door_open != 1) { - hongXiangStationDeviceDriver.writing("to_oven" - + hongXiangLocationInfo.getOvenNo() - + "_door" - + hongXiangLocationInfo.getShutterDoor() + "_open","2"); + // 下发开门指令 + String doorOpenCmd = "to_oven" + hongXiangLocationInfo.getOvenNo() + + "_door" + hongXiangLocationInfo.getShutterDoor() + "_open"; + LuceneLogDto doorOpenLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱指令] 下发开门指令: " + doorOpenCmd + " = 2") + .build(); + doorOpenLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(doorOpenLog); + + hongXiangStationDeviceDriver.writing(doorOpenCmd, "2"); return; } //到达取货点 //门已经开了,此时请求取料 // to_oven1_door1_storage - hongXiangStationDeviceDriver.writing("to_oven" - + hongXiangLocationInfo.getOvenNo() - + "_door" - + hongXiangLocationInfo.getShutterDoor() + "_storage",String.valueOf(hongXiangLocationInfo.getStorageNo())); + String storageCmd = "to_oven" + hongXiangLocationInfo.getOvenNo() + + "_door" + hongXiangLocationInfo.getShutterDoor() + "_storage"; + LuceneLogDto storageLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱指令] 下发取料指令: " + storageCmd + " = " + hongXiangLocationInfo.getStorageNo()) + .build(); + storageLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(storageLog); + + hongXiangStationDeviceDriver.writing(storageCmd, String.valueOf(hongXiangLocationInfo.getStorageNo())); data = ndcAgvService.sendAgvTwoModeInst(phase, index, 0); + + // AGV反馈日志 + LuceneLogDto agvFeedbackLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[AGV反馈] 取货点处理完成, phase:" + phase + ", 反馈数据:" + Arrays.toString(data)) + .build(); + agvFeedbackLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(agvFeedbackLog); + logServer.deviceExecuteLog(this.device_code, "", "", "agvphase:" + phase + "反馈:" + data); } else { LuceneLogDto logDto = LuceneLogDto.builder() @@ -431,44 +515,84 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic //取货完毕 // to_oven1_door1_storage - hongXiangStationDeviceDriver.writing("to_oven" - + hongXiangLocationInfo.getOvenNo() - + "_door" - + hongXiangLocationInfo.getShutterDoor() + "_completed","1"); + String completeCmd = "to_oven" + hongXiangLocationInfo.getOvenNo() + + "_door" + hongXiangLocationInfo.getShutterDoor() + "_completed"; + + // 烘箱取货完成日志 + LuceneLogDto ovenCompleteLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱交互] 取货完成, ovenNo:" + hongXiangLocationInfo.getOvenNo() + + ", shutterDoor:" + hongXiangLocationInfo.getShutterDoor()) + .build(); + ovenCompleteLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(ovenCompleteLog); + + LuceneLogDto completeCmdLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱指令] 下发取货完成指令: " + completeCmd + " = 1") + .build(); + completeCmdLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(completeCmdLog); + + hongXiangStationDeviceDriver.writing(completeCmd, "1"); } else { LuceneLogDto logDto = LuceneLogDto.builder() .device_code(this.getDeviceCode()) - .content("agvphase:" + phase + "反馈:" + data) + .content("[设备处理] 其他设备类型, agvphase:" + phase + ", data:" + data) .build(); logDto.setLog_level(4); luceneExecuteLogService.deviceExecuteLog(logDto); } - //更新agv状态 + //更新agv状态 - WCS系统交互 JSONObject param3 = new JSONObject(); param3.put("containerCode", inst.getVehicle_code()); param3.put("taskCode", inst.getTask_code()); param3.put("carId", inst.getCarno()); param3.put("taskType", inst.getInstruction_type()); param3.put("feedbackStatus", FeedbackStatusEnum.TAKE_FINISH.getCode()); - LuceneLogDto logDto3 = LuceneLogDto.builder() + + // WCS交互日志 + LuceneLogDto wcsLog = LuceneLogDto.builder() .device_code(device_code) - .content("取货完成,参数:" + param3) + .content("[WCS交互] 反馈取货完成, 参数:" + param3.toJSONString()) .build(); - logDto3.setLog_level(4); - luceneExecuteLogService.deviceExecuteLog(logDto3); + wcsLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(wcsLog); + HttpResponse httpResponse2 = acsToWcsService.feedbackTaskStatusToWcs(param3); + if (ObjectUtil.isNotEmpty(httpResponse2)) { - JSONObject param2 = new JSONObject(); - param2.put("device_code", device_code); - LuceneLogDto logDto2 = LuceneLogDto.builder() + String responseBody = httpResponse2.body(); + JSONObject responseJson = JSONObject.parseObject(responseBody); + int responseCode = responseJson.getIntValue("responseCode"); + + LuceneLogDto wcsResponseLog = LuceneLogDto.builder() .device_code(device_code) - .content("取货完成返回参数:" + param2) + .content("[WCS响应] 取货完成响应, status:" + httpResponse2.getStatus() + ", body:" + responseBody) .build(); - logDto2.setLog_level(4); - luceneExecuteLogService.deviceExecuteLog(logDto2); - + wcsResponseLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(wcsResponseLog); + + // 判断 responseCode 是否为 0,非 0 则返回 + if (responseCode != 0) { + LuceneLogDto errorLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[WCS响应] 取货完成失败, responseCode:" + responseCode + + ", responseMessage:" + responseJson.getString("responseMessage")) + .build(); + errorLog.setLog_level(2); + luceneExecuteLogService.deviceExecuteLog(errorLog); + return; + } + } else { + LuceneLogDto wcsNullLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[WCS响应] 取货完成响应为空") + .build(); + wcsNullLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(wcsNullLog); } data = ndcAgvService.sendAgvTwoModeInst(phase, index, 0); @@ -535,15 +659,28 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic luceneExecuteLogService.deviceExecuteLog(logDto3); HttpResponse httpResponse2 = acsToWcsService.feedbackTaskStatusToWcs(param3); if (ObjectUtil.isNotEmpty(httpResponse2)) { - JSONObject param2 = new JSONObject(); - param2.put("device_code", device_code); + String responseBody = httpResponse2.body(); + JSONObject responseJson = JSONObject.parseObject(responseBody); + int responseCode = responseJson.getIntValue("responseCode"); + LuceneLogDto logDto2 = LuceneLogDto.builder() .device_code(device_code) - .content("申请放货返回参数:" + param2) + .content("申请放货返回参数:" + responseBody) .build(); logDto2.setLog_level(4); luceneExecuteLogService.deviceExecuteLog(logDto2); - + + // 判断 responseCode 是否为 0,非 0 则返回 + if (responseCode != 0) { + LuceneLogDto errorLog = LuceneLogDto.builder() + .device_code(device_code) + .content("WCS反馈失败, responseCode:" + responseCode + + ", responseMessage:" + responseJson.getString("responseMessage")) + .build(); + errorLog.setLog_level(2); + luceneExecuteLogService.deviceExecuteLog(errorLog); + return; + } } if (device.getDeviceDriver() instanceof StandardOrdinarySiteDeviceDriver) { @@ -595,33 +732,73 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic } //烘箱 else if (device.getDeviceDriver() instanceof HongXiangStationDeviceDriver) { hongXiangStationDeviceDriver = (HongXiangStationDeviceDriver) device.getDeviceDriver(); + + // 烘箱放货点日志 + LuceneLogDto ovenPutLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱交互] 到达放货点, ovenNo:" + hongXiangLocationInfo.getOvenNo() + + ", shutterDoor:" + hongXiangLocationInfo.getShutterDoor() + + ", storageNo:" + hongXiangLocationInfo.getStorageNo()) + .build(); + ovenPutLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(ovenPutLog); //判断对应点位是卷帘门是否开门到位 int door_open = (int) hongXiangStationDeviceDriver.getPropertyValue("item_oven" + hongXiangLocationInfo.getOvenNo() + "_door" + hongXiangLocationInfo.getShutterDoor() + "_open"); + + // 门状态日志 + LuceneLogDto doorStatusLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱门状态] oven" + hongXiangLocationInfo.getOvenNo() + + "_door" + hongXiangLocationInfo.getShutterDoor() + "_open = " + door_open) + .build(); + doorStatusLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(doorStatusLog); + if (door_open != 1) { - hongXiangStationDeviceDriver.writing("to_oven" - + hongXiangLocationInfo.getOvenNo() - + "_door" - + hongXiangLocationInfo.getShutterDoor() + "_open","1"); + String doorOpenCmd = "to_oven" + hongXiangLocationInfo.getOvenNo() + + "_door" + hongXiangLocationInfo.getShutterDoor() + "_open"; + LuceneLogDto doorOpenLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱指令] 下发开门指令: " + doorOpenCmd + " = 1") + .build(); + doorOpenLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(doorOpenLog); + + hongXiangStationDeviceDriver.writing(doorOpenCmd, "1"); return; } - //门已经开了,此时请求取料 - // to_oven1_door1_storage - hongXiangStationDeviceDriver.writing("to_oven" - + hongXiangLocationInfo.getOvenNo() - + "_door" - + hongXiangLocationInfo.getShutterDoor() + "_storage",String.valueOf(hongXiangLocationInfo.getStorageNo())); + //门已经开了,此时请求放货 + String storageCmd = "to_oven" + hongXiangLocationInfo.getOvenNo() + + "_door" + hongXiangLocationInfo.getShutterDoor() + "_storage"; + LuceneLogDto storageLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱指令] 下发放货指令: " + storageCmd + " = " + hongXiangLocationInfo.getStorageNo()) + .build(); + storageLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(storageLog); + + hongXiangStationDeviceDriver.writing(storageCmd, String.valueOf(hongXiangLocationInfo.getStorageNo())); data = ndcAgvService.sendAgvTwoModeInst(phase, index, 0); + + // AGV反馈日志 + LuceneLogDto agvFeedbackLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[AGV反馈] 放货点处理完成, phase:" + phase + ", 反馈数据:" + Arrays.toString(data)) + .build(); + agvFeedbackLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(agvFeedbackLog); + logServer.deviceExecuteLog(this.device_code, "", "", "agvphase:" + phase + "反馈:" + data); } else { LuceneLogDto logDto = LuceneLogDto.builder() .device_code(this.getDeviceCode()) - .content("agvphase:" + phase + "反馈:" + data) + .content("[设备处理] 其他设备类型, agvphase:" + phase + ", data:" + data) .build(); logDto.setLog_level(4); luceneExecuteLogService.deviceExecuteLog(logDto); @@ -690,45 +867,85 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic //烘箱 else if (device.getDeviceDriver() instanceof HongXiangStationDeviceDriver) { hongXiangStationDeviceDriver = (HongXiangStationDeviceDriver) device.getDeviceDriver(); - hongXiangStationDeviceDriver.writing("to_oven" - + hongXiangLocationInfo.getOvenNo() - + "_door" - + hongXiangLocationInfo.getShutterDoor() + "_completed","1"); + + // 烘箱放货完成日志 + LuceneLogDto ovenPutCompleteLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱交互] 放货完成, ovenNo:" + hongXiangLocationInfo.getOvenNo() + + ", shutterDoor:" + hongXiangLocationInfo.getShutterDoor()) + .build(); + ovenPutCompleteLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(ovenPutCompleteLog); + + String completeCmd = "to_oven" + hongXiangLocationInfo.getOvenNo() + + "_door" + hongXiangLocationInfo.getShutterDoor() + "_completed"; + LuceneLogDto completeCmdLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[烘箱指令] 下发放货完成指令: " + completeCmd + " = 1") + .build(); + completeCmdLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(completeCmdLog); + + hongXiangStationDeviceDriver.writing(completeCmd, "1"); } else { LuceneLogDto logDto = LuceneLogDto.builder() .device_code(this.getDeviceCode()) - .content("agvphase:" + phase + "反馈:" + data) + .content("[设备处理] 其他设备类型, agvphase:" + phase + ", data:" + data) .build(); logDto.setLog_level(4); luceneExecuteLogService.deviceExecuteLog(logDto); } - //更新agv状态 + //更新agv状态 - WCS系统交互 JSONObject param3 = new JSONObject(); param3.put("containerCode", inst.getVehicle_code()); param3.put("taskCode", inst.getTask_code()); param3.put("carId", inst.getCarno()); param3.put("taskType", inst.getInstruction_type()); param3.put("feedbackStatus", FeedbackStatusEnum.PUT_FINISH.getCode()); - LuceneLogDto logDto3 = LuceneLogDto.builder() + + // WCS交互日志 + LuceneLogDto wcsLog = LuceneLogDto.builder() .device_code(device_code) - .content("取货完成,参数:" + param3) + .content("[WCS交互] 反馈放货完成, 参数:" + param3.toJSONString()) .build(); - logDto3.setLog_level(4); - luceneExecuteLogService.deviceExecuteLog(logDto3); + wcsLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(wcsLog); + HttpResponse httpResponse2 = acsToWcsService.feedbackTaskStatusToWcs(param3); + if (ObjectUtil.isNotEmpty(httpResponse2)) { - JSONObject param2 = new JSONObject(); - param2.put("device_code", device_code); - LuceneLogDto logDto2 = LuceneLogDto.builder() + String responseBody = httpResponse2.body(); + JSONObject responseJson = JSONObject.parseObject(responseBody); + int responseCode = responseJson.getIntValue("responseCode"); + + LuceneLogDto wcsResponseLog = LuceneLogDto.builder() .device_code(device_code) - .content("取货完成返回参数:" + param2) + .content("[WCS响应] 放货完成响应, status:" + httpResponse2.getStatus() + ", body:" + responseBody) .build(); - logDto2.setLog_level(4); - luceneExecuteLogService.deviceExecuteLog(logDto2); - + wcsResponseLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(wcsResponseLog); + + // 判断 responseCode 是否为 0,非 0 则返回 + if (responseCode != 0) { + LuceneLogDto errorLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[WCS响应] 放货完成失败, responseCode:" + responseCode + + ", responseMessage:" + responseJson.getString("responseMessage")) + .build(); + errorLog.setLog_level(2); + luceneExecuteLogService.deviceExecuteLog(errorLog); + return; + } + } else { + LuceneLogDto wcsNullLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[WCS响应] 放货完成响应为空") + .build(); + wcsNullLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(wcsNullLog); } data = ndcAgvService.sendAgvTwoModeInst(phase, index, 0); @@ -855,9 +1072,25 @@ public class AgvNdcTwoDeviceDriver extends AbstractDeviceDriver implements Devic // TwoNDC2SocketConnectionAutoRun.write(data); // } if (StrUtil.equals(inst.getAgv_system_type(), "2")) { + // AGV指令下发日志 + LuceneLogDto agvWriteLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[AGV指令下发] 发送数据到AGV, data:" + Arrays.toString(data)) + .build(); + agvWriteLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(agvWriteLog); + TwoNDCSocketConnectionAutoRun.write(data); } } + + // 方法出口日志 + LuceneLogDto exitLog = LuceneLogDto.builder() + .device_code(device_code) + .content("[processSocket] 处理AGV数据完成, phase:" + phase + "(0x" + Integer.toHexString(phase) + ")") + .build(); + exitLog.setLog_level(4); + luceneExecuteLogService.deviceExecuteLog(exitLog); }