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 } }