2026-01-26 18:56:53 +09:00

522 lines
21 KiB
C#

using Aurora.Api.Devices;
using AuroraDeviceCommon.Utils;
using Hermes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace AuroraLaser.Coherent.Compact
{
/*
// 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 LaserCompactMiniSerialConn : SerialConn, ILaserCoherentCompact
{
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 LaserCompactMiniSerialConn(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
}
}