|
|
@ -4,15 +4,19 @@ import a8k.a8k_can_protocol.*; |
|
|
|
import a8k.appbean.HardwareException; |
|
|
|
import a8k.appbean.ecode.AppRet; |
|
|
|
import a8k.base_hardware.A8kCanBusService; |
|
|
|
import a8k.service.db.dao.A8kTubeInfoDao; |
|
|
|
import a8k.service.db.dao.MotorTubeRackMoveCtrlServiceParameterDao; |
|
|
|
import com.iflytop.uf.util.UfCommon; |
|
|
|
import a8k.utils.*; |
|
|
|
import jakarta.annotation.Resource; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.slf4j.Logger; |
|
|
|
import org.slf4j.LoggerFactory; |
|
|
|
import org.springframework.stereotype.Component; |
|
|
|
|
|
|
|
@Component |
|
|
|
@HardwareService(name = "进出料控制模组") |
|
|
|
@HardwareServiceParams(service = MotorTubeRackMoveCtrlService.class) |
|
|
|
public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
HardwareParamReader hpReader = new HardwareParamReader(SamplesPreProcessModuleCtrlService.class); |
|
|
|
static Logger logger = LoggerFactory.getLogger(MotorTubeRackMoveCtrlService.class); |
|
|
|
|
|
|
|
/** |
|
|
|
* |
|
|
|
* 主要用来控制试管架的平移动作控制 |
|
|
@ -27,18 +31,70 @@ public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
* 2. 扫码时试管夹紧夹紧 |
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
A8kTubeInfoDao tubeInfoDao; |
|
|
|
|
|
|
|
@Resource |
|
|
|
private A8kCanBusService canBus; |
|
|
|
@Resource |
|
|
|
MotorTubeRackMoveCtrlServiceParameterDao paramDao; |
|
|
|
|
|
|
|
private A8kCanBusService canBus; |
|
|
|
@Resource |
|
|
|
private CommonHardwareOpeartion comHardwareOpear; |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "超时时间", group = "基础参数") |
|
|
|
public Integer getActionOvertime() { |
|
|
|
return 10000; |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "入料X位置", group = "试管架位置信息") |
|
|
|
public Integer getTREnterXPos() { |
|
|
|
return -51; |
|
|
|
} |
|
|
|
|
|
|
|
// public void setTREnterXPos(Integer val){ |
|
|
|
// return; |
|
|
|
// } |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "出料X位置", group = "试管架位置信息") |
|
|
|
public Integer getTRExitXPos() { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "试管架扫码X位置", group = "试管架位置信息") |
|
|
|
public Integer getTRScanXPos() { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "试管扫码位置", group = "试管位置信息") |
|
|
|
public Integer getTScanXPos() { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "试管高度判断位置", group = "试管位置信息") |
|
|
|
public Integer getTAltitJudgXPos() { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "试管是否存在判断位置", group = "试管位置信息") |
|
|
|
public Integer getTExistJudgXPos() { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "试管预处理位置", group = "试管位置信息") |
|
|
|
public Integer getTPreProcessXPos() { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "试管架孔间距", group = "其他") |
|
|
|
public Integer getTubeSpacing() { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "扫码舵机转速(0..900)", group = "其他") |
|
|
|
public Integer getTubeScanServoTorque() { |
|
|
|
return 300; |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceParam(name = "试管扫码超时时间", group = "其他") |
|
|
|
public Integer getTubeScanOvertime() { |
|
|
|
return 1000; |
|
|
|
} |
|
|
|
|
|
|
|
/*========================================================================================= |
|
|
|
* Overrid |
|
|
|
*========================================================================================*/ |
|
|
@ -49,6 +105,7 @@ public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
* @throws HardwareException 硬件异常 |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@HardwareServiceAction(name = "模块使能", group = "基础操作") |
|
|
|
public void modGroupEnable(Boolean enable) throws HardwareException { |
|
|
|
comHardwareOpear.enableAllMotor(enable); |
|
|
|
} |
|
|
@ -58,26 +115,32 @@ public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
* @throws HardwareException 硬件异常 |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@HardwareServiceAction(name = "模块停止", group = "基础操作") |
|
|
|
public void modGroupStop() throws HardwareException { |
|
|
|
comHardwareOpear.forceStopAllMOtor(); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
@HardwareServiceAction(name = "模块归零", group = "基础操作") |
|
|
|
public void modGroupMoveToZero() throws HardwareException, InterruptedException { |
|
|
|
if (!canBus.stepMotorReadIoState(MId.ShakeModGripperZM, 0)) { |
|
|
|
throw new HardwareException(A8kEcode.ShakeModGripperZMNotInZeroPos); |
|
|
|
} |
|
|
|
canBus.miniServoMoveTo(MId.ShakeModTubeScanerClampingSV, paramDao.getScanCodeReleasePos()); |
|
|
|
canBus.waitForMod(MId.ShakeModTubeScanerClampingSV, paramDao.getActionOvertime()); |
|
|
|
|
|
|
|
// if (!canBus.stepMotorReadIoState(MId.ShakeModGripperZM, 0)) { |
|
|
|
// throw new HardwareException(A8kEcode.ShakeModGripperZMNotInZeroPos); |
|
|
|
// } |
|
|
|
canBus.miniServoEnable(MId.ShakeModTubeScanerClampingSV, 1); |
|
|
|
canBus.miniServoMoveTo(MId.ShakeModTubeScanerClampingSV, 0); |
|
|
|
canBus.waitForMod(MId.ShakeModTubeScanerClampingSV, getActionOvertime()); |
|
|
|
|
|
|
|
canBus.stepMotorEnable(MId.FeedingModXM, 1); |
|
|
|
canBus.stepMotorEasyMoveToZero(MId.FeedingModXM); |
|
|
|
canBus.waitForMod(MId.FeedingModXM, paramDao.getActionOvertime()); |
|
|
|
canBus.waitForMod(MId.FeedingModXM, getActionOvertime()); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
@HardwareServiceAction(name = "模块快速归零", group = "基础操作") |
|
|
|
public void modGroupMoveToZeroQuick() throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(0); |
|
|
|
canBus.stepMotorEasyMoveToZero(MId.FeedingModXM); |
|
|
|
canBus.waitForMod(MId.FeedingModXM, paramDao.getActionOvertime()); |
|
|
|
canBus.waitForMod(MId.FeedingModXM, getActionOvertime()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
@ -91,57 +154,108 @@ public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
* @throws HardwareException 硬件异常 |
|
|
|
* @throws InterruptedException 打断异常 |
|
|
|
*/ |
|
|
|
public void moveTubeRackTo(int pos) throws HardwareException, InterruptedException { |
|
|
|
if (!canBus.stepMotorReadIoState(MId.ShakeModGripperZM, 0)) { |
|
|
|
throw new HardwareException(A8kEcode.ShakeModGripperZMNotInZeroPos); |
|
|
|
} |
|
|
|
@HardwareServiceAction(name = "移动到X", group = "模块基础动作") |
|
|
|
public void moveTubeRackTo(Integer pos) throws HardwareException, InterruptedException { |
|
|
|
// if (!canBus.stepMotorReadIoState(MId.ShakeModGripperZM, 0)) { |
|
|
|
// throw new HardwareException(A8kEcode.ShakeModGripperZMNotInZeroPos); |
|
|
|
// } |
|
|
|
scanClampModRelease(); |
|
|
|
|
|
|
|
canBus.stepMotorEnable(MId.FeedingModXM, 1); |
|
|
|
canBus.stepMotorEasyMoveTo(MId.FeedingModXM, pos); |
|
|
|
canBus.waitForMod(MId.FeedingModXM, paramDao.getActionOvertime()); |
|
|
|
canBus.waitForMod(MId.FeedingModXM, getActionOvertime()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 扫描夹紧机构夹紧 |
|
|
|
*/ |
|
|
|
@HardwareServiceAction(name = "扫描夹紧机构夹紧", group = "模块基础动作") |
|
|
|
public void scanClampModClamp() throws HardwareException, InterruptedException { |
|
|
|
canBus.miniServoMoveTo(MId.ShakeModTubeScanerClampingSV, paramDao.getScanCodeClampPos()); |
|
|
|
canBus.waitForMod(MId.ShakeModTubeScanerClampingSV, paramDao.getActionOvertime()); |
|
|
|
canBus.miniServoEnable(MId.ShakeModTubeScanerClampingSV, 1); |
|
|
|
canBus.miniServoMoveTo(MId.ShakeModTubeScanerClampingSV, 900); |
|
|
|
canBus.waitForMod(MId.ShakeModTubeScanerClampingSV, getActionOvertime()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 扫描夹紧机构复位 |
|
|
|
*/ |
|
|
|
@HardwareServiceAction(name = "扫描夹紧机构复位", group = "模块基础动作") |
|
|
|
public void scanClampModRelease() throws HardwareException, InterruptedException { |
|
|
|
canBus.miniServoMoveTo(MId.ShakeModTubeScanerClampingSV, paramDao.getScanCodeReleasePos()); |
|
|
|
canBus.waitForMod(MId.ShakeModTubeScanerClampingSV, paramDao.getActionOvertime()); |
|
|
|
canBus.miniServoEnable(MId.ShakeModTubeScanerClampingSV, 1); |
|
|
|
canBus.miniServoMoveTo(MId.ShakeModTubeScanerClampingSV, 0); |
|
|
|
canBus.waitForMod(MId.ShakeModTubeScanerClampingSV, getActionOvertime()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 入料 |
|
|
|
*/ |
|
|
|
@HardwareServiceAction(name = "入料", group = "模块基础动作") |
|
|
|
public void tryEnterTubeRack(Integer time) throws HardwareException, InterruptedException { |
|
|
|
try { |
|
|
|
canBus.stepMotorEasyRotate(MId.FeedingModInfeedM, 1); |
|
|
|
Thread.sleep(time); |
|
|
|
} finally { |
|
|
|
canBus.moduleStop(MId.FeedingModInfeedM); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 出料 |
|
|
|
*/ |
|
|
|
@HardwareServiceAction(name = "出料", group = "模块基础动作") |
|
|
|
public void tryEjectTubeRack(Integer time) throws HardwareException, InterruptedException { |
|
|
|
try { |
|
|
|
canBus.stepMotorEasyRotate(MId.FeedingModOutfeedM, 1); |
|
|
|
Thread.sleep(time); |
|
|
|
} finally { |
|
|
|
canBus.moduleStop(MId.FeedingModOutfeedM); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@HardwareServiceAction(name = "通过归零读取当前电机位置", group = "模块基础动作") |
|
|
|
public AppRet<Integer> readXPosByMoveZero() throws HardwareException, InterruptedException { |
|
|
|
|
|
|
|
|
|
|
|
canBus.stepMotorEnable(MId.FeedingModXM, 1); |
|
|
|
canBus.stepMotorEasyMoveToZero(MId.FeedingModXM); |
|
|
|
canBus.waitForMod(MId.FeedingModXM, getActionOvertime()); |
|
|
|
canBus.stepMotorEnable(MId.FeedingModXM, 0); |
|
|
|
return AppRet.success(-canBus.moduleGetReg(MId.FeedingModXM, RegIndex.kreg_step_motor_dpos)); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*========================================================================================= |
|
|
|
* 片段 |
|
|
|
*========================================================================================*/ |
|
|
|
|
|
|
|
/** |
|
|
|
* 移动试管架到入口位置 |
|
|
|
*/ |
|
|
|
@HardwareServiceAction(name = "移动<试管架>到入口位置", group = "片段") |
|
|
|
public void tubeRackMoveToEnterPos() throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(paramDao.getTubeRackEnterPos()); |
|
|
|
moveTubeRackTo(getTREnterXPos()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 移动试管架到出口位置 |
|
|
|
* 移动<试管架>到出口位置 |
|
|
|
*/ |
|
|
|
@HardwareServiceAction(name = "移动<试管架>到出口位置", group = "片段") |
|
|
|
public void moveTubeRackToExitPos() throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(paramDao.getTubeRackExitPos()); |
|
|
|
moveTubeRackTo(getTRExitXPos()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 移动试管架到试管架扫码位置 |
|
|
|
* 移动<试管架>到试管架扫码位置 |
|
|
|
*/ |
|
|
|
public void moveTubeRackToScanPos() throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(paramDao.getTubeRackScanCodePos()); |
|
|
|
private void moveTubeRackToScanPos() throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(getTRScanXPos()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 移动试管架到试管架扫码并扫码 |
|
|
|
* 移动试管架到扫码并扫码 |
|
|
|
*/ |
|
|
|
@HardwareServiceAction(name = "扫描试管架编码", group = "片段") |
|
|
|
public AppRet<String> moveTubeRackToScanPosAndScan() throws HardwareException, InterruptedException { |
|
|
|
String result; |
|
|
|
moveTubeRackToScanPos(); |
|
|
@ -152,7 +266,6 @@ public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
scanClampModRelease(); |
|
|
|
return AppRet.success(result); |
|
|
|
} |
|
|
|
|
|
|
|
// |
|
|
|
// 试管移动 |
|
|
|
// |
|
|
@ -161,10 +274,10 @@ public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
* 移动试管到扫码位置 |
|
|
|
* @param tubeIndex 试管索引 |
|
|
|
*/ |
|
|
|
public AppRet<Object> moveTubeToScanPos(Integer tubeIndex) throws HardwareException, InterruptedException { |
|
|
|
var scanPos = paramDao.getTubeScanPos() + tubeIndex * paramDao.getTubeSpacing(); |
|
|
|
private AppRet<Object> moveTubeToScanPos(Integer tubeIndex) throws HardwareException, InterruptedException { |
|
|
|
var scanPos = getTScanXPos() + tubeIndex * getTubeSpacing(); |
|
|
|
canBus.stepMotorEasyMoveTo(MId.FeedingModXM, scanPos); |
|
|
|
canBus.waitForMod(MId.FeedingModXM, paramDao.getActionOvertime()); |
|
|
|
canBus.waitForMod(MId.FeedingModXM, getActionOvertime()); |
|
|
|
return AppRet.success(); |
|
|
|
} |
|
|
|
|
|
|
@ -172,6 +285,7 @@ public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
* 移动试管到扫码位置,并扫码 |
|
|
|
* @param tubeIndex 试管索引 |
|
|
|
*/ |
|
|
|
@HardwareServiceAction(name = "扫描<试管X>编码", group = "片段") |
|
|
|
public AppRet<String> moveTubeToScanPosAndScan(Integer tubeIndex) throws HardwareException, InterruptedException { |
|
|
|
var ret = this.moveTubeToScanPos(tubeIndex); |
|
|
|
if (!ret.isSuccess()) {return AppRet.fail(ret);} |
|
|
@ -179,8 +293,8 @@ public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
try { |
|
|
|
scanClampModClamp(); |
|
|
|
canBus.codeScanerStartScan(MId.FeedingModScannerMod); |
|
|
|
canBus.miniServoRotateWithTorque(MId.ShakeModTubeScanerClampingSV, paramDao.getTubeScanServoTorque()); |
|
|
|
String result = canBus.codeScanerWaittingForResult(MId.FeedingModScannerMod, paramDao.getTubeScanOvertime()); |
|
|
|
canBus.miniServoRotateWithTorque(MId.ShakeModTubeScanerClampingSV, getTubeScanServoTorque()); |
|
|
|
String result = canBus.codeScanerWaittingForResult(MId.FeedingModScannerMod, getTubeScanOvertime()); |
|
|
|
return AppRet.success(result); |
|
|
|
} finally { |
|
|
|
canBus.moduleStop(MId.ShakeModTubeScanerClampingSV); |
|
|
@ -193,66 +307,57 @@ public class MotorTubeRackMoveCtrlService implements HardwareCtrlModule { |
|
|
|
* 移动试管到试管高低判断位置 |
|
|
|
* @param tubeIndex 试管索引 |
|
|
|
*/ |
|
|
|
public void moveTubeToHeighJudgmentPos(Integer tubeIndex) throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(paramDao.getTubeHeighJudgmentPos() + tubeIndex * paramDao.getTubeSpacing()); |
|
|
|
@HardwareServiceAction(name = "判断<试管N>位置", group = "片段") |
|
|
|
public AppRet<Boolean> moveTubeToAltitJudgXPos(Integer tubeIndex) throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(getTAltitJudgXPos() + tubeIndex * getTubeSpacing()); |
|
|
|
return AppRet.success(isHighTube()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 移动试管到试管预处理的位置 |
|
|
|
* @param tubeIndex 试管索引 |
|
|
|
*/ |
|
|
|
@HardwareServiceAction(name = "移动<试管N>摇匀位", group = "片段") |
|
|
|
public void moveTubeToPreProcessPos(Integer tubeIndex) throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(paramDao.getTubePreProcessPos() + tubeIndex * paramDao.getTubeSpacing()); |
|
|
|
moveTubeRackTo(getTPreProcessXPos() + tubeIndex * getTubeSpacing()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 移动试管到试管有无判断位置 |
|
|
|
* @param tubeIndex 试管索引 |
|
|
|
*/ |
|
|
|
public void moveTubeToExistJudgmentPos(Integer tubeIndex) throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(paramDao.getTubeExistJudgmentPos() + tubeIndex * paramDao.getTubeSpacing()); |
|
|
|
} |
|
|
|
|
|
|
|
public void tryEnterTubeRack(Integer time) throws HardwareException, InterruptedException { |
|
|
|
try { |
|
|
|
canBus.stepMotorEasyRotate(MId.FeedingModInfeedM, 1); |
|
|
|
Thread.sleep(time); |
|
|
|
} finally { |
|
|
|
canBus.moduleStop(MId.FeedingModInfeedM); |
|
|
|
} |
|
|
|
@HardwareServiceAction(name = "判断<试管N>是否存在", group = "片段") |
|
|
|
public AppRet<Boolean> judgeTubeExist(Integer tubeIndex) throws HardwareException, InterruptedException { |
|
|
|
moveTubeRackTo(getTExistJudgXPos() + tubeIndex * getTubeSpacing()); |
|
|
|
return AppRet.success(isTubeRackInEnterPos()); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public void tryEjectTubeRack(Integer time) throws HardwareException, InterruptedException { |
|
|
|
try { |
|
|
|
canBus.stepMotorEasyRotate(MId.FeedingModOutfeedM, 1); |
|
|
|
Thread.sleep(time); |
|
|
|
} finally { |
|
|
|
canBus.moduleStop(MId.FeedingModOutfeedM); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// |
|
|
|
// 状态 |
|
|
|
// |
|
|
|
|
|
|
|
public AppRet<Boolean> isTubeRackInEnterPos() throws HardwareException { |
|
|
|
return AppRet.success(canBus.getIOState(IOId.InfeedPPS)); |
|
|
|
@HardwareServiceStatus(name = "入口光电") |
|
|
|
public Boolean isTubeRackInEnterPos() throws HardwareException { |
|
|
|
return canBus.getIOState(IOId.InfeedPPS); |
|
|
|
} |
|
|
|
|
|
|
|
public AppRet<Boolean> isTubeRackInExitPos() throws HardwareException { |
|
|
|
return AppRet.success(canBus.getIOState(IOId.OutfeedPPS)); |
|
|
|
@HardwareServiceStatus(name = "出口光电") |
|
|
|
public Boolean isTubeRackInExitPos() throws HardwareException { |
|
|
|
return canBus.getIOState(IOId.OutfeedPPS); |
|
|
|
} |
|
|
|
|
|
|
|
public AppRet<Boolean> isHighTube() throws HardwareException { |
|
|
|
return AppRet.success(canBus.getIOState(IOId.TubeHeightPPS)); |
|
|
|
@HardwareServiceStatus(name = "高低判读光电") |
|
|
|
public Boolean isHighTube() throws HardwareException { |
|
|
|
return canBus.getIOState(IOId.TubeHeightPPS); |
|
|
|
} |
|
|
|
|
|
|
|
public AppRet<Boolean> getTHchOuterPPS() throws HardwareException { |
|
|
|
return AppRet.success(canBus.getIOState(IOId.THChOuterPPS)); |
|
|
|
@HardwareServiceStatus(name = "通道外光电") |
|
|
|
public Boolean getTHchOuterPPS() throws HardwareException { |
|
|
|
return canBus.getIOState(IOId.THChOuterPPS); |
|
|
|
} |
|
|
|
|
|
|
|
public AppRet<Boolean> getTHchInterPPS() throws HardwareException { |
|
|
|
return AppRet.success(canBus.getIOState(IOId.THChInterPPS)); |
|
|
|
@HardwareServiceStatus(name = "通道内光电") |
|
|
|
public Boolean getTHchInterPPS() throws HardwareException { |
|
|
|
return canBus.getIOState(IOId.THChInterPPS); |
|
|
|
} |
|
|
|
} |