Merge branch 'test-zhouyin' into test-training2

This commit is contained in:
tiger_zhou 2022-08-09 17:21:32 +08:00
commit 50bb136082
3 changed files with 881 additions and 317 deletions

View File

@ -37,34 +37,7 @@ public class DriverOperateHandler {
public void changeTrainForce(Simulation simulation, String groupNumber, Float percent) { public void changeTrainForce(Simulation simulation, String groupNumber, Float percent) {
Objects.requireNonNull(percent); Objects.requireNonNull(percent);
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber); VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
// if (train.isSignalEB() && percent == -2) ATPService.changeTrainForce(train, percent);
// ATPService.cancelSignalEB(train);
// 门选不是零位不给牵引力
if (!train.getDoorSelection().equals(VirtualRealityTrain.DoorSelection.Z)) {
return;
}
if ((percent <= 1 && percent >= -1) || percent == -2) {
train.setLeverPosition(percent);
}
if (train.isEB()) {
return;
}
if (percent <= 1 && percent >= -1) {
if (percent > 0) {
float fk = train.getCurrentFkMax() * percent;
train.leaverUpdateTBForce(fk, 0);
} else if (percent < 0) {
float fb = train.getCurrentFbMax() * Math.abs(percent);
train.leaverUpdateTBForce(0, fb);
} else {
train.leaverUpdateTBForce(0, 0);
}
} else if (percent == -2) {
train.leaverUpdateTBForce(0, 350);
} else {
throw new SimulationException(SimulationExceptionType.Illegal_Argument,
String.format("数值[%s]超限,列车牵引/制动力可调整比例范围应该在[-1, 1]之间,或为快速制动-2", percent));
}
} }
/** /**
@ -82,6 +55,7 @@ public class DriverOperateHandler {
/** /**
* 修改工况手轮档位 * 修改工况手轮档位
*
* @param simulation * @param simulation
* @param groupNumber * @param groupNumber
* @param gear * @param gear
@ -210,4 +184,137 @@ public class DriverOperateHandler {
public void stopTrain(Simulation simulation, SimulationMember simulationMember, String groupNumber, Boolean eb) { public void stopTrain(Simulation simulation, SimulationMember simulationMember, String groupNumber, Boolean eb) {
simulationRobotService.stopTrain(simulation, simulationMember, groupNumber, eb); simulationRobotService.stopTrain(simulation, simulationMember, groupNumber, eb);
} }
/**
* 确认运行至前方站
*/
@OperateHandlerMapping(type = Operation.Type.Drive_Ahead)
public void driveAhead(Simulation simulation, SimulationMember simulationMember, String groupNumber) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.driveAhead(simulation, groupNumber);
}
/**
* 进路闭塞法行车
*/
@OperateHandlerMapping(type = Operation.Type.Route_Block_Drive)
public void routeBlockDrive(Simulation simulation, SimulationMember simulationMember, String groupNumber) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.routeBlockDrive(simulation, groupNumber);
}
/**
* 越红灯行驶
*/
@OperateHandlerMapping(type = Operation.Type.Drive_Through_The_Red_Light)
public void driveThroughTheRedLight(Simulation simulation, SimulationMember simulationMember, String groupNumber) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.driveThroughTheRedLight(simulation, groupNumber);
}
/**
* 越引导信号行驶
*/
@OperateHandlerMapping(type = Operation.Type.Drive_Through_The_Guide_Signal)
public void driveThroughTheGuideSignal(Simulation simulation, SimulationMember simulationMember, String groupNumber) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.driveThroughTheGuideSignal(simulation, groupNumber);
}
/**
* 开关门
*/
@OperateHandlerMapping(type = Operation.Type.Open_Or_Close_Door)
public void openOrCloseDoor(Simulation simulation, SimulationMember simulationMember, String groupNumber) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.openOrCloseDoor(simulation, groupNumber);
}
/**
* 设置限速
*/
@OperateHandlerMapping(type = Operation.Type.Set_Speed_Limit)
public void setSpeedLimit(Simulation simulation, SimulationMember simulationMember, String groupNumber, Float speedLimit) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.setSpeedLimit(simulation, groupNumber, speedLimit);
}
/**
* 驾驶至
*/
@OperateHandlerMapping(type = Operation.Type.Drive_To)
public void driveTo(Simulation simulation, SimulationMember simulationMember, String groupNumber, String sectionCode) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.driveTo(simulation, groupNumber, sectionCode);
}
/**
* 回库
*/
@OperateHandlerMapping(type = Operation.Type.Inbound)
public void inbound(Simulation simulation, SimulationMember simulationMember, String groupNumber) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.inbound(simulation, groupNumber);
}
/**
* 修改预选模式
*/
@OperateHandlerMapping(type = Operation.Type.Change_Preselection_Mode)
public void changePreselectionMode(Simulation simulation, SimulationMember simulationMember, String groupNumber
, VirtualRealityTrain.PreselectionMode preselectionMode) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.changePreselectionMode(simulation, groupNumber, preselectionMode);
}
/**
* 转NRM模式
*/
@OperateHandlerMapping(type = Operation.Type.Apply_NRM)
public void applyNRM(Simulation simulation, SimulationMember simulationMember, String groupNumber) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.applyNRM(simulation, groupNumber);
}
/**
* 大铁发车
**/
@OperateHandlerMapping(type = Operation.Type.Depart_Train)
public void departTrain(Simulation simulation, SimulationMember simulationMember, String groupNumber) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.departTrain(simulation, groupNumber);
}
/**
* 大铁停车
*/
@OperateHandlerMapping(type = Operation.Type.Parking_Train)
public void parkingTrain(Simulation simulation, SimulationMember simulationMember, String groupNumber) {
if (!SimulationMember.Type.DRIVER.equals(simulationMember.getType())) {
throw new SimulationException(SimulationExceptionType.Member_Is_Not_Driver);
}
ATPService.parkingTrain(simulation, groupNumber);
}
} }

View File

@ -5,23 +5,27 @@ import club.joylink.rtss.simulation.cbtc.ATP.ground.MaService;
import club.joylink.rtss.simulation.cbtc.CI.CiApiService; import club.joylink.rtss.simulation.cbtc.CI.CiApiService;
import club.joylink.rtss.simulation.cbtc.CI.device.CiStandService; import club.joylink.rtss.simulation.cbtc.CI.device.CiStandService;
import club.joylink.rtss.simulation.cbtc.Simulation; import club.joylink.rtss.simulation.cbtc.Simulation;
import club.joylink.rtss.simulation.cbtc.constant.DriveMode; import club.joylink.rtss.simulation.cbtc.constant.*;
import club.joylink.rtss.simulation.cbtc.constant.RunLevel;
import club.joylink.rtss.simulation.cbtc.constant.SimulationConstants;
import club.joylink.rtss.simulation.cbtc.constant.SimulationModule;
import club.joylink.rtss.simulation.cbtc.data.CalculateService; import club.joylink.rtss.simulation.cbtc.data.CalculateService;
import club.joylink.rtss.simulation.cbtc.data.SimulationDataRepository;
import club.joylink.rtss.simulation.cbtc.data.map.Section; import club.joylink.rtss.simulation.cbtc.data.map.Section;
import club.joylink.rtss.simulation.cbtc.data.map.Signal;
import club.joylink.rtss.simulation.cbtc.data.map.Stand; import club.joylink.rtss.simulation.cbtc.data.map.Stand;
import club.joylink.rtss.simulation.cbtc.data.support.MovementAuthority; import club.joylink.rtss.simulation.cbtc.data.support.MovementAuthority;
import club.joylink.rtss.simulation.cbtc.data.support.RoutePath;
import club.joylink.rtss.simulation.cbtc.data.support.SectionPosition; import club.joylink.rtss.simulation.cbtc.data.support.SectionPosition;
import club.joylink.rtss.simulation.cbtc.data.vo.TrainInfo;
import club.joylink.rtss.simulation.cbtc.data.vr.VirtualRealityTrain; import club.joylink.rtss.simulation.cbtc.data.vr.VirtualRealityTrain;
import club.joylink.rtss.simulation.cbtc.data.vr.VirtualRealityTrain.PreselectionMode; import club.joylink.rtss.simulation.cbtc.data.vr.VirtualRealityTrain.PreselectionMode;
import club.joylink.rtss.simulation.cbtc.exception.SimulationException;
import club.joylink.rtss.simulation.cbtc.exception.SimulationExceptionType;
import club.joylink.rtss.simulation.cbtc.onboard.ATO.SpeedCurve; import club.joylink.rtss.simulation.cbtc.onboard.ATO.SpeedCurve;
import club.joylink.rtss.simulation.cbtc.onboard.ATO.service.ATOService; import club.joylink.rtss.simulation.cbtc.onboard.ATO.service.ATOService;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -491,4 +495,352 @@ public class ATPService {
closeATO(train); closeATO(train);
} }
} }
/**
* 确认运行至前方站
*/
public void driveAhead(Simulation simulation, String groupNumber) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
// 列车车头所在区段
Section section = train.getHeadPosition().getSection();
// 停车目的区段
SectionPosition standStopPosition;
if (section.isStandTrack()) {
standStopPosition = new SectionPosition(section, section.getStopPointByDirection(train.isRight()));
} else {
standStopPosition = train.calculateNextStandStopPosition();
if (standStopPosition == null && train.getTarget() != null) {
standStopPosition = new SectionPosition(train.getTarget(), train.getTarget().getStopPointByDirection(train.isRight()));
}
BusinessExceptionAssertEnum.OPERATION_NOT_SUPPORTED.assertNotNull(standStopPosition, train.debugStr() + "找不到下一个停车点");
}
if (!train.isStopAtThePosition(standStopPosition)) { //如果列车没停到目标位置
if (!train.isInTheGear(VirtualRealityTrain.Handwheel.MANUAL)) { // 改变工况手轮档位
changeGear(train, VirtualRealityTrain.Handwheel.MANUAL);
}
float percent = train.isEB() && (!train.isStop() || !train.isLeverNotInTractionGear())
? -2F : (!train.isStop() ? -1F : 0);
if (percent != 0) {
changeTrainForce(train, percent); // 改变列车的牵引/
}
if ((!DriveMode.RM.equals(train.getDriveMode()) || train.isEB()) && !train.isNRMMode()) {
changePreselectionMode(train, PreselectionMode.RM); // 修改预选模式
}
train.setRobotTargetPosition(standStopPosition);
train.getRobotDriveParam().setRun(true);
train.getRobotDriveParam().setStop(false);
}
}
/**
* 进路闭塞法行车
*/
public void routeBlockDrive(Simulation simulation, String groupNumber) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
if (!train.isAMMode()) {
changePreselectionMode(train, PreselectionMode.AM_C);
}
}
/**
* 越红灯行驶
*/
public void driveThroughTheRedLight(Simulation simulation, String groupNumber) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
if (!DriveMode.RM.equals(train.getDriveMode()) && !train.isNRMMode()) {
changePreselectionMode(train, PreselectionMode.RM);
}
if (train.isSignalEB()) {
releaseEB(train);
}
train.setRobotTargetPosition(train.calculateStopPosition4CrossTheRedLight()); //如果列车没停到目标位置
train.getRobotDriveParam().setThroughSignal(train.getHeadPosition().getSection().getSignalOf(train.isRight()));
}
/**
* 越引导信号行驶
*/
public void driveThroughTheGuideSignal(Simulation simulation, String groupNumber) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
if (!train.isRMMode() && !train.isNRMMode()) { //既不是RM也不是NRM切换预选模式
changePreselectionMode(train, PreselectionMode.RM);
}
if (!train.isInTheGear(VirtualRealityTrain.Handwheel.MANUAL)) { //转手轮
changeGear(train, VirtualRealityTrain.Handwheel.MANUAL);
}
train.setRobotTargetPosition(calculateStepPosition4CrossTheGuideSignal(train)); //如果列车没停到目标位置
train.getRobotDriveParam().setThroughSignal(train.getHeadPosition().getSection().getSignalOf(train.isRight()));
}
/**
* 开关门
*/
public void openOrCloseDoor(Simulation simulation, String groupNumber) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
List<Stand> standList = train.getHeadPosition().getSection().getStandList();
if (!CollectionUtils.isEmpty(standList) && standList.size() == 1 && standList.get(0).isSmall()) {
// 小站台停车无需开关门
return;
}
boolean doorIsRight = false, open = false;
if (!train.getDoorByDirection(false).isCloseAndLock()) { //列车左门没关
doorIsRight = false;
open = false;
} else if (!train.getDoorByDirection(true).isCloseAndLock()) { //列车右门没关
doorIsRight = true;
open = false;
} else {
Stand stand = standList.get(0);
if (stand.isInside() && stand.isRight()) { // 内测
if (stand.isRight()) { // 右站台开1门
doorIsRight = train.judgeDirection4DoorIsRight(train.getDoor1());
} else { // 左站台开2门
doorIsRight = train.judgeDirection4DoorIsRight(train.getDoor2());
}
} else { //外侧
if (stand.isRight()) { // 右站台开2门
doorIsRight = train.judgeDirection4DoorIsRight(train.getDoor2());
} else { // 左站台开1门
doorIsRight = train.judgeDirection4DoorIsRight(train.getDoor1());
}
}
open = true;
}
VirtualRealityTrain.Door door = train.getDoorByDirection(doorIsRight);
if (!Objects.equals(door.isOpen(), open)) {
openOrCloseDoor(simulation, train, doorIsRight, open);
}
}
/**
* 设置限速
*/
public void setSpeedLimit(Simulation simulation, String groupNumber, Float speedLimit) {
if (speedLimit != null) {
return;
}
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
if (speedLimit < 0) {
train.setSpeedLimit(Float.MAX_VALUE);
} else {
train.setSpeedLimit(speedLimit / 3.6f);
}
}
/**
* 驾驶至
*/
public void driveTo(Simulation simulation, String groupNumber, String sectionCode) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
Section section = simulation.getRepository().getByCode(sectionCode, Section.class);
SectionPosition targetPosition = new SectionPosition(section, section.getStopPointByDirection(train.isRight()));
train.setRobotTargetPosition(targetPosition);
train.getRobotDriveParam().setRun(true);
train.getRobotDriveParam().setStop(false);
}
/**
* 回库
*/
public void inbound(Simulation simulation, String groupNumber) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
Section headSection = train.getHeadPosition().getSection();
if (headSection.isTransferTrack() && train.isStop()) { //列车停在折返轨
SimulationDataRepository repository = simulation.getRepository();
TrainInfo trainInfo = repository.getSupervisedTrainByGroup(train.getGroupNumber());
List<RoutePath> routePathList = repository.queryRoutePathsByEnd(headSection);
if (routePathList.get(0).isRight() == trainInfo.getRight()) { //准备回库
trainInfo.finishPlanPrepareInbound();
}
}
}
/**
* 修改预选模式
*/
public void changePreselectionMode(Simulation simulation, String groupNumber, VirtualRealityTrain.PreselectionMode preselectionMode) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
if (!train.isAtpOn()) {
this.openAtp(train);
}
if (train.isSignalEB()) {
this.releaseEB(train);
}
PreselectionMode trainMode = train.getPreselectionMode();
if (trainMode != preselectionMode) { //预选级别不对
changePreselectionMode(train, preselectionMode);
} else if (trainMode.isMatchTheDriveMode(DriveMode.AM) && !train.isAMMode()) {
if (!train.isInTheGear(VirtualRealityTrain.Handwheel.ATO)) {
this.changeGear(train, VirtualRealityTrain.Handwheel.ATO);
}
if (!train.isLeverInCoastingGear()) {
this.changeTrainForce(train, 0F); // 改变列车的牵引
}
if (!train.isAtoOn()) {
this.openATO(train);
}
}
}
/**
* 转NRM模式
*/
public void applyNRM(Simulation simulation, String groupNumber) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
if (!train.isNRMMode()) {
this.cutOffAtp(train);
}
}
/**
* 大铁发车
**/
public void departTrain(Simulation simulation, String groupNumber) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
Section targetSection = getTargetSection(train.isRight(), train.getHeadPosition().getSection());
SectionPosition targetPosition = new SectionPosition(targetSection, targetSection.getStopPointByDirection(train.isRight()));
train.setRobotTargetPosition(targetPosition);
train.getRobotDriveParam().setStop(false);
train.getRobotDriveParam().setRun(true);
}
/**
* 大铁停车
*/
public void parkingTrain(Simulation simulation, String groupNumber) {
VirtualRealityTrain train = simulation.getRepository().getOnlineTrainBy(groupNumber);
train.setRobotTargetPosition(train.getHeadPosition());
train.getRobotDriveParam().setStop(true);
train.getRobotDriveParam().setRun(false);
}
/**
* 改变列车的牵引
*/
public void changeTrainForce(VirtualRealityTrain train, Float percent) {
// 门选不是零位不给牵引力
if (!train.getDoorSelection().equals(VirtualRealityTrain.DoorSelection.Z)) {
return;
}
if ((percent <= 1 && percent >= -1) || percent == -2) {
train.setLeverPosition(percent);
}
if (train.isEB()) {
return;
}
if (percent <= 1 && percent >= -1) {
if (percent > 0) {
float fk = train.getCurrentFkMax() * percent;
train.leaverUpdateTBForce(fk, 0);
} else if (percent < 0) {
float fb = train.getCurrentFbMax() * Math.abs(percent);
train.leaverUpdateTBForce(0, fb);
} else {
train.leaverUpdateTBForce(0, 0);
}
} else if (percent == -2) {
train.leaverUpdateTBForce(0, 350);
} else {
throw new SimulationException(SimulationExceptionType.Illegal_Argument,
String.format("数值[%s]超限,列车牵引/制动力可调整比例范围应该在[-1, 1]之间,或为快速制动-2", percent));
}
}
/**
* 修改预选模式
*
* @param train 列车
* @param preselectionMode 预选模式
*/
private void changePreselectionMode(VirtualRealityTrain train, PreselectionMode preselectionMode) {
if (train.getPreselectionMode() != preselectionMode) {
if (train.getTempPreselectionMode() != preselectionMode) {
if (preselectionMode.isHigherThan(train.getTempPreselectionMode())) {
this.preselectionModeUp(train);
} else {
this.preselectionModeDown(train);
}
} else {
this.confirmMessage(train);
}
}
}
private SectionPosition calculateStepPosition4CrossTheGuideSignal(VirtualRealityTrain train) {
boolean right = train.isRight();
SectionPosition headPosition = train.getHeadPosition();
Section section = headPosition.getSection();
Signal trainHeadSignal = section.getSignalOf(right);
if (trainHeadSignal == null || !trainHeadSignal.isGuideAspect()) {
throw new SimulationException(SimulationExceptionType.Invalid_Operation, "指令要求:列车车头所在区段有同向信号机并且开放引导信号");
}
SectionPosition sectionPosition = null;
for (int i = 0; i < 30; i++) {
section = section.findNextRunningSectionBaseRealSwitch(right);
BusinessExceptionAssertEnum.OPERATION_FAIL.assertNotNull(section, "引导信号前方未找到停车位置");
Signal signal = section.getSignalOf(right);
if (section.isStandTrack() || section.isTurnBackTrack()) {
sectionPosition = new SectionPosition(section, section.getStopPointByDirection(right));
break;
} else if (signal != null && !signal.isShunting() && !signal.isMainAspect()) { //同向信号机未正常开放
sectionPosition = new SectionPosition(section, signal.getOffset());
break;
}
}
if (sectionPosition == null) {
throw new SimulationException(SimulationExceptionType.Invalid_Operation, "引导信号前方未找到停车位置");
}
return sectionPosition;
}
private void releaseEB(VirtualRealityTrain train) {
if (!train.isInTheGear(VirtualRealityTrain.Handwheel.MANUAL)) {
changeGear(train, VirtualRealityTrain.Handwheel.MANUAL);
}
if (!train.isStop() || !train.isLeverNotInTractionGear()) {
changeTrainForce(train, -2F); // 改变列车的牵引/
}
if (!train.isRMMode()) {
changePreselectionMode(train, PreselectionMode.RM);
}
}
/**
* 从此区段向right方向获取未被占用的目标区段
*
* @param isRight 方向
* @param section 起始区段
* @return 目标区段
*/
private Section getTargetSection(boolean isRight, Section section) {
boolean loop = true;
Signal signal;
Section nextSection = section, targetSection = null;
while (loop) {
signal = isRight ? nextSection.getSignalToRight() : nextSection.getSignalToLeft(); // 方向信号机
// 信号机未开放直接中断
if (signal != null && SignalAspect.R.equals(signal.getAspect())) {
targetSection = nextSection;
break;
}
// 存在锁闭进路获取进路末端
if (signal != null && signal.getLockedRoute() != null) {
nextSection = signal.getLockedRoute().getDestination().getSection();
} else if (nextSection.isRouteLock()) { // 如果当前区段进路锁闭
nextSection = nextSection.getRoute().getDestination().getSection();
} else if (nextSection.isSwitchTrack()) { // 是邻近岔道
nextSection = nextSection.getNextRunningSectionOf(isRight);
} else {
nextSection = nextSection.getNextSection(isRight);
}
// 存在邻近区段,赋值
if (nextSection != null) {
targetSection = nextSection;
}
// 进路存在且不占用不出故障
loop = nextSection != null && !nextSection.isOccupied() && !nextSection.isFaultLock();
}
return targetSection;
}
} }