519 lines
21 KiB
C#
519 lines
21 KiB
C#
using Aurora.Api.Devices;
|
|
using AuroraDeviceCommon.Utils;
|
|
using Hermes;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
|
|
namespace AuroraLaser.Coherent
|
|
{
|
|
/*
|
|
// Coherent 는 일반적인 Coherent 사 Laser 장치를 지원함 (Compact: Compact 시리즈 의미)
|
|
// _1. Compact Mini (STC 솔더링)
|
|
*/
|
|
|
|
class CLASS_REGISTER
|
|
{
|
|
public STRUCT_STATUS_REGISTER m_stStatus_Regster;
|
|
public STRUCT_ERROR_REGISTER m_stError_Register;
|
|
public STRUCT_LASER_MODE_REGISTER m_stMode_Register;
|
|
|
|
public void Clear_Register()
|
|
{
|
|
m_stStatus_Regster = new STRUCT_STATUS_REGISTER();
|
|
m_stError_Register = new STRUCT_ERROR_REGISTER();
|
|
m_stMode_Register = new STRUCT_LASER_MODE_REGISTER();
|
|
m_stStatus_Regster.SerialNumber = string.Empty;
|
|
}
|
|
|
|
public void Set_Status_Register(short sValue)
|
|
{
|
|
bool bTemp = false;
|
|
// 구조체 중 처음 5개는 bool 변수
|
|
for (int i = 0; i < 5; i++) {
|
|
bTemp = !((sValue & (short)Math.Pow(2, i)) == 0);
|
|
switch (i) {
|
|
case 0:
|
|
m_stStatus_Regster.bPowerOn = bTemp;
|
|
break;
|
|
case 1:
|
|
m_stStatus_Regster.bLaserOn = bTemp;
|
|
break;
|
|
case 2:
|
|
m_stStatus_Regster.bErrorStatus = bTemp;
|
|
break;
|
|
case 3:
|
|
m_stStatus_Regster.bFatalError = bTemp;
|
|
break;
|
|
case 4:
|
|
m_stStatus_Regster.bEmission = bTemp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Set_Laser_Mode_Register(short sValue)
|
|
{
|
|
bool bTemp = false;
|
|
// 구조체는 모두 bool 변수
|
|
for (int i = 0; i < 5; i++) {
|
|
// 해당 비트가 0 이면, false, 1 이면 true
|
|
bTemp = !((sValue & (short)Math.Pow(2, i)) == 0);
|
|
switch (i) {
|
|
case 0:
|
|
m_stMode_Register.bPeltierCooler_HeatingCooling = bTemp;
|
|
break;
|
|
case 1:
|
|
m_stMode_Register.bCurrent_Set_External = bTemp;
|
|
break;
|
|
case 2:
|
|
m_stMode_Register.bLaser_Control_External = bTemp;
|
|
break;
|
|
case 3:
|
|
m_stMode_Register.bPulserOn = bTemp;
|
|
break;
|
|
case 4:
|
|
m_stMode_Register.bVT100On = bTemp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Set_Error_Register(short sValue)
|
|
{
|
|
bool bTemp = false;
|
|
m_stError_Register.ErrMsg = string.Empty;
|
|
// 구조체는 처음 10개는 bool 변수
|
|
for (int i = 0; i < 10; i++) {
|
|
bTemp = !((sValue & (short)Math.Pow(2, i)) == 0);
|
|
switch (i) {
|
|
case 0:
|
|
m_stError_Register.bPeltier_Cooler_Over_Temp = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "Perltier Cooler : Over Temperature\r\n";
|
|
break;
|
|
case 1:
|
|
m_stError_Register.bPeltier_Cooler_Under_Temp = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "Peltier Cooler : Under Temperature\r\n";
|
|
break;
|
|
case 2:
|
|
m_stError_Register.bFiber_Over_Temp = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "Fiber Over Temperature\r\n";
|
|
break;
|
|
case 3:
|
|
m_stError_Register.bFiber_Plug_Control_Error = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "Fiber Plug Control Error\r\n";
|
|
break;
|
|
case 4:
|
|
m_stError_Register.bError_During_Laser_On = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "Error During Laser Error\r\n";
|
|
break;
|
|
case 5:
|
|
m_stError_Register.bPeltier_Cooler_Temp_Upper_40 = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "Over Temp Peltier Cooler : Temp > 40℃\r\n";
|
|
break;
|
|
case 6:
|
|
m_stError_Register.bPeltier_Cooler_Temp_Under_10 = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "Under Temp Peltier Cooler : Temp < 10℃\r\n";
|
|
break;
|
|
case 7:
|
|
m_stError_Register.bOver_Temp_Diode = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "Over Temp Diode : Temp > 48℃\r\n";
|
|
break;
|
|
case 8:
|
|
m_stError_Register.bUnder_Temp_Diode = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "Under Temp Diode : Temp < 5℃\r\n";
|
|
break;
|
|
case 9:
|
|
m_stError_Register.bUSB_Interface_Broken = bTemp;
|
|
if (bTemp) m_stError_Register.ErrMsg += "USB Interface Broken While Operation\r\n";
|
|
break;
|
|
}
|
|
}
|
|
if (m_stError_Register.ErrMsg.Length != 0) m_stError_Register.ErrMsg = Utils.StrLeft(m_stError_Register.ErrMsg, m_stError_Register.ErrMsg.Length - 2);
|
|
}
|
|
}
|
|
|
|
internal partial class CompactMini : SerialConn, ILaserCompact
|
|
{
|
|
private SerialConnConfig _connConfig;
|
|
private LaserCompactMiniConfig _rawConfig;
|
|
|
|
public double LaserTargetPower { get; set; }
|
|
public bool NeedConfigSave { get; set; }
|
|
|
|
public CLASS_REGISTER m_Register = new CLASS_REGISTER();
|
|
public STRUCT_LASER_POWERMODEL stPowerModel;
|
|
|
|
public bool IsNetworkError { get; private set; }
|
|
public bool IsLaserError { get; private set; }
|
|
bool _isSimul; // 나중에 필요 없으면 지우자
|
|
|
|
const int asciiCR = 13; // carriage return (\r)
|
|
|
|
#region 생성자
|
|
public CompactMini(string deviceId, bool isSimul = false) : base(deviceId, isSimul)
|
|
{
|
|
stPowerModel = new STRUCT_LASER_POWERMODEL(0, null, null); // Laser 파워 교정 초기화
|
|
_isSimul = isSimul;
|
|
}
|
|
#endregion
|
|
|
|
#region 통신 상태
|
|
public int Connect(LaserCompactMiniConfig conn)
|
|
{
|
|
_connConfig = conn.ConnConfig as SerialConnConfig;
|
|
_rawConfig = conn;
|
|
|
|
return ConnectAndInit();
|
|
}
|
|
|
|
public int ConnectAndInit()
|
|
{
|
|
try {
|
|
LoadLaserCalibrationData();
|
|
m_Register.Clear_Register();
|
|
|
|
if (IsPortOpen()) { // 시뮬은 여기서 리턴
|
|
return E1.OK;
|
|
}
|
|
|
|
bool bRet = false;
|
|
if (Open(_connConfig) == E1.OK) {
|
|
string strRtn = Laser_CMD_Send(CMD_GetSerialNumber); // Rdn : Read out serial number of device
|
|
//--- 레이저 ID 식별 로직
|
|
// 다수 레이저 장치가 있는 경우, USB 컴포트라 PC 재부팅에 따라서 COM포트가 바뀌는 경우가 있다고 함
|
|
// 1. 시리얼 넘버가 빈문자열이면, 초기 상태로 가정하고, 연결을 허용한다.
|
|
if (string.IsNullOrEmpty(_rawConfig.SerialNumber)) {
|
|
_rawConfig.SerialNumber = strRtn; // 시리얼 넘버를 저장
|
|
NeedConfigSave = true; // Config 저장 필요...
|
|
}
|
|
// 연결된 레이저의 시리얼 넘버와 일치하는지 검사
|
|
if (strRtn.Length > 0 && strRtn.Contains(_rawConfig.SerialNumber)) {
|
|
bRet = LaserInit();
|
|
} else {
|
|
Close();
|
|
Log.E($"SN mismatch: {_rawConfig.SerialNumber} ≠ {strRtn}");
|
|
bRet = false;
|
|
}
|
|
}
|
|
|
|
if (bRet) {
|
|
IsNetworkError = false;
|
|
return E1.OK;
|
|
} else {
|
|
IsNetworkError = true;
|
|
return E1.NETWORK;
|
|
}
|
|
} catch {
|
|
IsNetworkError = true;
|
|
return E2.NOT_CONNECTED;
|
|
}
|
|
}
|
|
|
|
public int Disconnect()
|
|
{
|
|
IsLaserError = false;
|
|
return Close();
|
|
}
|
|
|
|
public int Reconnect()
|
|
{
|
|
// 1. 포트 닫기
|
|
if (Disconnect() != E1.OK) {
|
|
Log.E("Reconnect: Port Close is failed");
|
|
}
|
|
// 2. 연결
|
|
var res = ConnectAndInit();
|
|
return res;
|
|
}
|
|
#endregion
|
|
|
|
#region 레이저 관련, 통신 R/W API 포함
|
|
public void ErrorReset()
|
|
{
|
|
if (Laser_CMD_Send(CMD_ResetError) != "OK") {
|
|
Log.E("Error Reset Fail");
|
|
}
|
|
}
|
|
|
|
public bool SetOutputPower(double powerPerc)
|
|
{
|
|
// SerialConn 에 뮤텍스 있음
|
|
// 범위 [0, 1000]
|
|
int iPow = (int)(Math.Round(powerPerc, 1) * 10);
|
|
if (m_Register.m_stStatus_Regster.PowerValue == iPow) return true;
|
|
|
|
Laser_CMD_Send($"{CMD_SetLaserPower}{iPow}"); // 리턴: "OK" or "ERROR"
|
|
Thread.Sleep(10);
|
|
string strRtn1 = Laser_CMD_Send(CMD_GetInternalSetPower); // Rdk : Read out value of internal set (laser) power
|
|
if (strRtn1 != string.Empty) {
|
|
if (int.TryParse(strRtn1, out int iRtn)) {
|
|
m_Register.m_stStatus_Regster.PowerValue = iRtn;
|
|
}
|
|
}
|
|
// 송신값과 수신값 비교로 레이저에 파워값이 세팅되었는지 체크
|
|
return m_Register.m_stStatus_Regster.PowerValue == iPow;
|
|
}
|
|
|
|
string Laser_CMD_Send(string strCMD)
|
|
{
|
|
string strSendMsg = strCMD + Convert.ToChar(asciiCR); // 13: CR
|
|
if (!ReadExisting(strSendMsg, out string strRtn, Convert.ToChar(asciiCR))) {
|
|
IsNetworkError = true;
|
|
return string.Empty;
|
|
}
|
|
// else : isCommError = false;
|
|
|
|
// 전후 CR, LF, 공백 제거
|
|
return strRtn.Trim("\r\n ".ToCharArray());
|
|
}
|
|
|
|
bool LaserInit()
|
|
{
|
|
bool flInternelMode = false;
|
|
|
|
Laser_CMD_Send(CMD_SetPowerOff); // 파워 off
|
|
Laser_CMD_Send(CMD_SetVT100ModeOff); // VT100 모드 off
|
|
// 무조건 External 이 실행됨
|
|
if (flInternelMode) Laser_CMD_Send(CMD_SourceForLaserControlByInternal); // Source for laser control : internal
|
|
else Laser_CMD_Send(CMD_SourceForLaserControlByExternal); // external
|
|
Laser_CMD_Send(CMD_SourceForPresettingLaserCurrentByInternal); // source for presetting the laser current (laser output power): internal
|
|
Laser_CMD_Send(CMD_GetLaserMode); // Read out operating mode of the laser device
|
|
|
|
return !IsNetworkError;
|
|
}
|
|
|
|
#region Common
|
|
public STRUCT_LASER_MODE_REGISTER GetLaserModeInfo()
|
|
{
|
|
return m_Register.m_stMode_Register;
|
|
}
|
|
|
|
public STRUCT_STATUS_REGISTER GetLaserStatusInfo()
|
|
{
|
|
return m_Register.m_stStatus_Regster;
|
|
}
|
|
|
|
public STRUCT_ERROR_REGISTER GetLaserErrMsgInfo()
|
|
{
|
|
return m_Register.m_stError_Register;
|
|
}
|
|
#endregion
|
|
|
|
#region Power calibration
|
|
// 제너릭 List 로 (double perc, double watt) 튜플 형태로 바꾸자..
|
|
public List<(double perc, double watt)> _powerCalData = new List<(double perc, double watt)>();
|
|
public void LoadLaserCalibrationData()
|
|
{
|
|
try {
|
|
// 교정 모델 정보 읽어오기
|
|
stPowerModel._modelType = (eModelType)_rawConfig.ModelType;
|
|
stPowerModel._paramA[0] = _rawConfig.ModelParamA0;
|
|
stPowerModel._paramB[0] = _rawConfig.ModelParamB0;
|
|
stPowerModel._paramA[1] = _rawConfig.ModelParamA1;
|
|
stPowerModel._paramB[1] = _rawConfig.ModelParamB1;
|
|
|
|
// 최소 단위는 0.1 [%] 이고, 범위는 [0, 100] 이므로, 최대 1001 개가 존재 가능함
|
|
_powerCalData.Clear();
|
|
|
|
if (_rawConfig.PowerValue.Count != 1001) {
|
|
Log.W($"Total No. of Config's Power Measure Data is not equal to 1001!: ${_rawConfig.PowerValue.Count}");
|
|
}
|
|
foreach (var item in _rawConfig.PowerValue) {
|
|
_powerCalData.Add((Double.Parse(item.Key), item.Value));
|
|
}
|
|
} catch (Exception ex) {
|
|
Log.W($"LoadLaserCalibrationData() is failed: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public List<(double perc, double watt)> GetPowerCalData()
|
|
{
|
|
return _powerCalData;
|
|
}
|
|
|
|
public double Watt2Power(double watt)
|
|
{
|
|
return Watt2PowerLogic(watt, stPowerModel._modelType);
|
|
}
|
|
|
|
public double Watt2Power(double watt, eModelType calibType)
|
|
{
|
|
return Watt2PowerLogic(watt, calibType);
|
|
}
|
|
|
|
public double Power2Watt(double powerPerc)
|
|
{
|
|
// 예외 조건. INI 파일이 없어서, No data 인 경우,,
|
|
if (_powerCalData.Count == 0) { // 예외 조건..
|
|
return 0;
|
|
}
|
|
|
|
try {
|
|
double l_powerWatt = 0.0;
|
|
switch (stPowerModel._modelType) {
|
|
case eModelType.LUT:
|
|
case eModelType.Lin1Seg: {
|
|
// 시뮬레이션이니까, 1차 모델을 적용하자...
|
|
// 모델식은 Y [%] = a * X [W] + b 이므로,
|
|
// Y - b = a * X
|
|
// X [W] = (Y - b) / a
|
|
// 예외 조건 1: a != 0.0
|
|
// 예외 조건 2: X >= 0.0
|
|
l_powerWatt = (powerPerc - stPowerModel._paramB[0]) / stPowerModel._paramA[0];
|
|
break;
|
|
}
|
|
case eModelType.Lin2Seg: {
|
|
if (powerPerc < 50.0) {
|
|
l_powerWatt = (powerPerc - stPowerModel._paramB[0]) / stPowerModel._paramA[0];
|
|
} else {
|
|
l_powerWatt = (powerPerc - stPowerModel._paramB[1]) / stPowerModel._paramA[1];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (l_powerWatt < 0.0) {
|
|
l_powerWatt = 0.0;
|
|
}
|
|
|
|
return l_powerWatt;
|
|
} catch (Exception ex) {
|
|
Log.E($"Exception: {ex.Message}");
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
private double Watt2PowerLogic(double watt, eModelType modelType)
|
|
{
|
|
try {
|
|
double _powerPerc = 0.0;
|
|
switch (modelType) {
|
|
case eModelType.LUT: {
|
|
double dblSubtractValue = 0;
|
|
double remdblSubtractValue = 10000;
|
|
double dblFindPowerValue = 0;
|
|
|
|
foreach (var item in _powerCalData) {
|
|
// dblPowerCalData: ini 파일에서 읽어온 데이터
|
|
dblSubtractValue = Math.Abs(item.watt - watt);
|
|
// 최소값 찾는류의 로직인가??
|
|
if (dblSubtractValue < remdblSubtractValue) {
|
|
dblFindPowerValue = item.perc;
|
|
remdblSubtractValue = dblSubtractValue;
|
|
}
|
|
}
|
|
_powerPerc = dblFindPowerValue;
|
|
break;
|
|
}
|
|
case eModelType.Lin1Seg: {
|
|
// 시뮬레이션이니까, 1차 모델을 적용하자...
|
|
// 모델식은 Y [%] = a * X [W] + b 임,
|
|
// 예외 조건 1: 0 <= Y <= 100
|
|
_powerPerc = stPowerModel._paramA[0] * watt + stPowerModel._paramB[0];
|
|
break;
|
|
}
|
|
case eModelType.Lin2Seg: {
|
|
// 모델식은 Y [%] = a * X [W] + b 임,
|
|
// 2번째 세그먼트의 Y 의 범위는 [50, 100] [%] 임
|
|
// 따라서 세그먼트의 기준값 [W] 를 찾자...
|
|
// Y - b = a * X ==> X = (Y - b) / a
|
|
// X [W] = (50 - b[1]) / a[1]
|
|
var beginWattFor2Seg = (50.0 - stPowerModel._paramB[1]) / stPowerModel._paramA[1];
|
|
if (watt < beginWattFor2Seg) {
|
|
_powerPerc = stPowerModel._paramA[0] * watt + stPowerModel._paramB[0];
|
|
} else {
|
|
_powerPerc = stPowerModel._paramA[1] * watt + stPowerModel._paramB[1];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (_powerPerc < 0.0) {
|
|
_powerPerc = 0.0;
|
|
} else if (_powerPerc > 100.0) {
|
|
_powerPerc = 100.0;
|
|
}
|
|
|
|
// 소수점 1의 자리까지, Round to nearest even 방식
|
|
return Math.Round(_powerPerc, 1);
|
|
} catch (Exception ex) {
|
|
Log.E($"Exception: {ex.Message}");
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
public void SetLaserPowerCalibMethod(string powerCalibMethod)
|
|
{
|
|
// 예외: 같다면 리턴
|
|
if (string.Equals(powerCalibMethod, stPowerModel._modelType.ToString())) {
|
|
return;
|
|
}
|
|
|
|
if (powerCalibMethod == eModelType.LUT.ToString()) {
|
|
stPowerModel._modelType = eModelType.LUT;
|
|
} else if (powerCalibMethod == eModelType.Lin1Seg.ToString()) {
|
|
// 예외 검사 필요...???
|
|
// 기울기가 일정 이하로 작으면, calibration 이 안된것으로 판단하자...
|
|
// 판단 기준은 얼마가 좋을까??
|
|
if (stPowerModel._paramA[0] < 0.3) {
|
|
Log.W("Please check the Laser calib. status. Perhaps, there is no calibration model");
|
|
return;
|
|
}
|
|
stPowerModel._modelType = eModelType.Lin1Seg;
|
|
} else if (powerCalibMethod == eModelType.Lin2Seg.ToString()) {
|
|
if (stPowerModel._paramA[0] < 0.5) {
|
|
Log.W("Please check the Laser calib. status. Perhaps, there is no calibration model");
|
|
return;
|
|
}
|
|
stPowerModel._modelType = eModelType.Lin2Seg;
|
|
} else {
|
|
Log.E($"Not supported CalibMethod: {powerCalibMethod}");
|
|
}
|
|
}
|
|
|
|
public string GetLaserPowerCalibMethod()
|
|
{
|
|
return stPowerModel._modelType.ToString();
|
|
}
|
|
#endregion
|
|
|
|
public void Update()
|
|
{
|
|
short sRtn = 0;
|
|
|
|
string strRtn = Laser_CMD_Send(CMD_GetErrorRegister); // : error register
|
|
if (strRtn != string.Empty) {
|
|
sRtn = Utils.Hex2short(strRtn.Replace("h", string.Empty));
|
|
IsLaserError = sRtn > 0; // laser error status
|
|
m_Register.Set_Error_Register(sRtn);
|
|
}
|
|
Thread.Sleep(10);
|
|
|
|
strRtn = Laser_CMD_Send(CMD_GetLaserMode); //Rdm : laser mode
|
|
if (strRtn != string.Empty) {
|
|
sRtn = Utils.Hex2short(strRtn.Replace("h", string.Empty));
|
|
m_Register.Set_Laser_Mode_Register(sRtn);
|
|
}
|
|
Thread.Sleep(10);
|
|
|
|
strRtn = Laser_CMD_Send(CMD_GetStatusRegister); //Rdx : Status register
|
|
if (strRtn != string.Empty) {
|
|
sRtn = Utils.Hex2short(strRtn.Replace("h", string.Empty));
|
|
m_Register.Set_Status_Register(sRtn);
|
|
}
|
|
Thread.Sleep(10);
|
|
|
|
strRtn = Laser_CMD_Send(CMD_GetTemperature); //Rdh : Read out laser diode temperature(0~250도)
|
|
if (strRtn != string.Empty) {
|
|
if (int.TryParse(strRtn, out int iRet)) m_Register.m_stStatus_Regster.Temp = iRet;
|
|
}
|
|
Thread.Sleep(10);
|
|
|
|
strRtn = Laser_CMD_Send(CMD_GetInternalSetPower); //RdK : Read out internal laser power
|
|
if (strRtn != string.Empty) {
|
|
if (int.TryParse(strRtn, out int iRet)) m_Register.m_stStatus_Regster.PowerValue = iRet;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|