887 lines
46 KiB
C#
887 lines
46 KiB
C#
using System;
|
|
using System.Configuration;
|
|
using System.Data;
|
|
using System.Data.OleDb;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace AuroraPLC
|
|
{
|
|
public abstract partial class PLCBase
|
|
{
|
|
#region PLC 엑셀파일 경로
|
|
//const string _alarmSettingPath = "External\\PLC\\PLC_DeviceMap_SettingAlarm.xlsx";
|
|
|
|
string _bitInputPath = "External\\PLC\\PLC_DeviceMap_Bit_Input_B_Device";
|
|
string _bitOutputPath = "External\\PLC\\PLC_DeviceMap_Bit_Output_B_Device";
|
|
string _mBitInputPath = "External\\PLC\\PLC_DeviceMap_Bit_Input_M_Device";
|
|
string _mBitOutputPath = "External\\PLC\\PLC_DeviceMap_Bit_Output_M_Device";
|
|
string _wordInputPath = "External\\PLC\\PLC_DeviceMap_Word_Input_W_Device";
|
|
string _wordOutputPath = "External\\PLC\\PLC_DeviceMap_Word_Output_W_Device";
|
|
string _dWordInputPath = "External\\PLC\\PLC_DeviceMap_Word_Input_D_Device";
|
|
string _dWordOutputPath = "External\\PLC\\PLC_DeviceMap_Word_Output_D_Device";
|
|
|
|
/*
|
|
public string AlarmSettingPath {
|
|
get {
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
return Path.Combine(recipePath, _alarmSettingPath);
|
|
}
|
|
}
|
|
*/
|
|
|
|
public string BitInputPath {
|
|
get {
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
|
|
if (mmapFileType == PLCMMapFileType.CSV) {
|
|
return Path.Combine(recipePath, _bitInputPath + ".csv");
|
|
} else {
|
|
return Path.Combine(recipePath, _bitInputPath + ".xlsx");
|
|
}
|
|
}
|
|
}
|
|
|
|
public string BitOutputPath {
|
|
get {
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
var m_connConfig = _connConfig as PLCMXComponentConnConfig;
|
|
|
|
if (mmapFileType == PLCMMapFileType.CSV) {
|
|
return Path.Combine(recipePath, _bitOutputPath + ".csv");
|
|
} else {
|
|
return Path.Combine(recipePath, _bitOutputPath + ".xlsx");
|
|
}
|
|
}
|
|
}
|
|
public string MBitInputPath {
|
|
get {
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
if (mmapFileType == PLCMMapFileType.CSV) {
|
|
return Path.Combine(recipePath, _mBitInputPath + ".csv");
|
|
} else {
|
|
return Path.Combine(recipePath, _mBitInputPath + ".xlsx");
|
|
}
|
|
}
|
|
}
|
|
|
|
public string MBitOutputPath {
|
|
get {
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
if (mmapFileType == PLCMMapFileType.CSV) {
|
|
return Path.Combine(recipePath, _mBitOutputPath + ".csv");
|
|
} else {
|
|
return Path.Combine(recipePath, _mBitOutputPath + ".xlsx");
|
|
}
|
|
}
|
|
}
|
|
|
|
public string WordInputPath {
|
|
get {
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
if (mmapFileType == PLCMMapFileType.CSV) {
|
|
return Path.Combine(recipePath, _wordInputPath + ".csv");
|
|
} else {
|
|
return Path.Combine(recipePath, _wordInputPath + ".xlsx");
|
|
}
|
|
}
|
|
}
|
|
|
|
public string WordOutputPath {
|
|
get {
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
if (mmapFileType == PLCMMapFileType.CSV) {
|
|
return Path.Combine(recipePath, _wordOutputPath + ".csv");
|
|
} else {
|
|
return Path.Combine(recipePath, _wordOutputPath + ".xlsx");
|
|
}
|
|
}
|
|
}
|
|
public string DWordInputPath {
|
|
get {
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
if (mmapFileType == PLCMMapFileType.CSV) {
|
|
return Path.Combine(recipePath, _dWordInputPath + ".csv");
|
|
} else {
|
|
return Path.Combine(recipePath, _dWordInputPath + ".xlsx");
|
|
}
|
|
}
|
|
}
|
|
|
|
public string DWordOutputPath {
|
|
get {
|
|
var recipePath = ConfigurationManager.AppSettings["DATA_PATH"];
|
|
if (mmapFileType == PLCMMapFileType.CSV) {
|
|
return Path.Combine(recipePath, _dWordOutputPath + ".csv");
|
|
} else {
|
|
return Path.Combine(recipePath, _dWordOutputPath + ".xlsx");
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
protected PLCMMapFileType mmapFileType;
|
|
|
|
#region CSV 파일 방식 파서
|
|
|
|
/*
|
|
// BOM 이 있어야 쓸 수 있음..
|
|
protected static Encoding GetEncoding(string fileName)
|
|
{
|
|
// Read the BOM (Byte Order Mark)
|
|
var bom = new byte[4];
|
|
using (var file = new FileStream(fileName, FileMode.Open, FileAccess.Read)) {
|
|
file.Read(bom, 0, 4);
|
|
}
|
|
|
|
// Analyze the BOM
|
|
if (bom[0] == 0x2B && bom[1] == 0x2F && bom[2] == 0x76) {
|
|
return Encoding.UTF7;
|
|
}
|
|
if (bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF) {
|
|
return Encoding.UTF8;
|
|
}
|
|
if (bom[0] == 0xFF && bom[1] == 0xFE) {
|
|
return Encoding.Unicode; // UTF-16LE
|
|
}
|
|
if (bom[0] == 0xFE && bom[1] == 0xFF) {
|
|
return Encoding.BigEndianUnicode; // UTF-16BE
|
|
}
|
|
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xFE && bom[3] == 0xFF) {
|
|
return Encoding.UTF32;
|
|
}
|
|
return Encoding.Default;
|
|
}
|
|
*/
|
|
|
|
private void AllocateMemBuffer(int index, ref string[] memLines, out int startAddress, out int endAddress, out ePLCErrCode retCode)
|
|
{
|
|
retCode = ePLCErrCode.OK;
|
|
startAddress = 0;
|
|
endAddress = 0;
|
|
|
|
// 시작 주소와 끝 주소를 찾아서 전체 주소 범위를 계산하여 버퍼 범위를 할당
|
|
// 필수조건
|
|
// - 시작 주소 블럭(16개)은 무조건 2번째 라인에 위치
|
|
// - 마지막 주소 블럭(16개)은 끝에 위치 (최소한 끝에서 5개 라인 내에 위치해야 함)
|
|
|
|
// 1.1 시작 주소 찾기
|
|
if (memLines.Length == 1) {
|
|
return;
|
|
}
|
|
var startAddrLine = memLines[1];
|
|
// 예외 검사
|
|
if (string.IsNullOrEmpty(startAddrLine)) {
|
|
Log.E($"[{DeviceId}] Invalid start address: index: {index}");
|
|
// 리턴으로 에러 알람을 발생시켜야 겠는데 !!
|
|
retCode = ePLCErrCode.MemMapPaser_InValid_StartAddress;
|
|
return;
|
|
}
|
|
var strTokens = startAddrLine.Split(','); // 토큰 분리
|
|
if (strTokens.Length < 3) { // 주소, 영문, 국문이 없으면 무효한 라인이므로, 스킵
|
|
Log.E($"[{DeviceId}] Invalid start address line: index: {index}");
|
|
retCode = ePLCErrCode.MemMapPaser_InValid_StartAddress;
|
|
return;
|
|
}
|
|
// 앞뒤로 불필요한 공백 제거, B/W 문자 제거하여 정수로 변환
|
|
if (index == 0 || index == 1 || index == 4 || index == 5) {
|
|
startAddress = int.Parse(strTokens[0].ToUpper().Trim().Substring(1), System.Globalization.NumberStyles.HexNumber);
|
|
} else if (index == 2 || index == 3 || index == 6 || index == 7) {
|
|
startAddress = int.Parse(strTokens[0].ToUpper().Trim().Substring(1), System.Globalization.NumberStyles.Number);
|
|
}
|
|
|
|
// 1.2 마지막 주소 찾기
|
|
for (int k = memLines.Length; k > memLines.Length - 5; --k) {
|
|
var endAddrLine = memLines[k - 1];
|
|
// 빈 라인이면 스킵
|
|
if (string.IsNullOrEmpty(endAddrLine)) {
|
|
continue;
|
|
}
|
|
var strTokensEnd = endAddrLine.Split(','); // 토큰 분리
|
|
if (strTokensEnd.Length < 3) { // 주소, 영문, 국문이 없으면 무효한 라인이므로, 스킵
|
|
Log.E($"[{DeviceId}] Invalid end address line: index: {index}");
|
|
retCode = ePLCErrCode.MemMapPaser_InValid_EndAddress;
|
|
return;
|
|
}
|
|
if (index == 0 || index == 1 || index == 4 || index == 5) {
|
|
endAddress = int.Parse(strTokensEnd[0].ToUpper().Trim().Substring(1), System.Globalization.NumberStyles.HexNumber);
|
|
} else if (index == 2 || index == 3 || index == 6 || index == 7) {
|
|
endAddress = int.Parse(strTokensEnd[0].ToUpper().Trim().Substring(1), System.Globalization.NumberStyles.Number);
|
|
}
|
|
// 성공
|
|
break;
|
|
}
|
|
// 버퍼에 할당할 메모리 크기 계산
|
|
int memBufferSize = endAddress - startAddress + 1;
|
|
if (memBufferSize <= 0) {
|
|
Log.E($"[{DeviceId}] Invalid memory size (Start/End addresses are wrong): index: {index}");
|
|
retCode = ePLCErrCode.MemMapPaser_InValid_MemorySize;
|
|
return;
|
|
}
|
|
|
|
// 각 case 에 따라 메모리 크기 할당
|
|
switch (index) {
|
|
case 0: // B Device Bit Input
|
|
for (int idx = 0; idx < memBufferSize; ++idx) {
|
|
BitInputs.Add(
|
|
(startAddress + idx,
|
|
new AddrCommentList(string.Empty, string.Empty))
|
|
);
|
|
}
|
|
break;
|
|
case 1: // B Device Bit Output
|
|
for (int idx = 0; idx < memBufferSize; ++idx) {
|
|
BitOutputs.Add(
|
|
(startAddress + idx,
|
|
new AddrCommentList(string.Empty, string.Empty))
|
|
);
|
|
}
|
|
break;
|
|
case 2: // M Device Bit Input
|
|
for (int idx = 0; idx < memBufferSize; ++idx) {
|
|
MBitInputs.Add(
|
|
(startAddress + idx,
|
|
new AddrCommentList(string.Empty, string.Empty))
|
|
);
|
|
}
|
|
break;
|
|
case 3: // M Device Bit Output
|
|
for (int idx = 0; idx < memBufferSize; ++idx) {
|
|
MBitOutputs.Add(
|
|
(startAddress + idx,
|
|
new AddrCommentList(string.Empty, string.Empty))
|
|
);
|
|
}
|
|
break;
|
|
case 4: // W Device Word Input
|
|
for (int idx = 0; idx < memBufferSize; ++idx) {
|
|
WordInputs.Add(
|
|
(startAddress + idx,
|
|
new AddrCommentList(string.Empty, string.Empty))
|
|
);
|
|
}
|
|
break;
|
|
case 5: // W Device Word Output
|
|
for (int idx = 0; idx < memBufferSize; ++idx) {
|
|
WordOutputs.Add(
|
|
(startAddress + idx,
|
|
new AddrCommentList(string.Empty, string.Empty))
|
|
);
|
|
}
|
|
break;
|
|
case 6: // D Device Word Input
|
|
//for (int idx = 0; idx < memBufferSize; ++idx) {
|
|
// DWordInputs.Add(
|
|
// (startAddress + idx,
|
|
// new AddrCommentList(string.Empty, string.Empty))
|
|
// );
|
|
//}
|
|
break;
|
|
case 7: // D Device Word Output
|
|
//for (int idx = 0; idx < memBufferSize; ++idx) {
|
|
// DWordOutputs.Add(
|
|
// (startAddress + idx,
|
|
// new AddrCommentList(string.Empty, string.Empty))
|
|
// );
|
|
//}
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected bool ParsePLCBitWordAddressFromCSV(string bitInputPath, string bitOutputPath, string wordInputPath, string wordOutputPath, out ePLCErrCode retCode)
|
|
{
|
|
retCode = ePLCErrCode.OK;
|
|
|
|
string[] strPath = new string[8];
|
|
|
|
//이전과의 호환성 고려, 만약 경로를 설정한 경우(해당 경로로 읽기)
|
|
//var m_connConfig = _connConfig as PLCMXComponentConnConfig;
|
|
//if (m_connConfig.PLCInterfaceMapDO != null) {
|
|
// BitInputPath = m_connConfig.PLCInterfaceMapDO.ToString();
|
|
//}
|
|
|
|
int index = 0;
|
|
try {
|
|
strPath[0] = BitInputPath;
|
|
strPath[1] = BitOutputPath;
|
|
strPath[2] = MBitInputPath;
|
|
strPath[3] = MBitOutputPath;
|
|
strPath[4] = WordInputPath;
|
|
strPath[5] = WordOutputPath;
|
|
strPath[6] = DWordInputPath;
|
|
strPath[7] = DWordOutputPath;
|
|
// 각 CSV 파일 파싱
|
|
for (int i = 0; i < strPath.Length; ++i) {
|
|
index = i;
|
|
if (File.Exists(strPath[i])) {
|
|
//var allLines = File.ReadAllLines(strPath[i], Encoding.Default);
|
|
var allLines = File.ReadAllLines(strPath[i], Encoding.UTF8);
|
|
//var allLines = File.ReadAllLines(strPath[i], GetEncoding(strPath[i]));
|
|
|
|
AllocateMemBuffer(i, ref allLines, out int startAddress, out int endAddress, out ePLCErrCode retCodeAllocateBuffer);
|
|
if (retCodeAllocateBuffer != ePLCErrCode.OK) { // 에러
|
|
retCode = retCodeAllocateBuffer;
|
|
// 에러가 발생하면, 중단하고 빠져나가자...(다음 메모리맵 파일을 파싱하는게 더 나을까???)
|
|
return false;
|
|
}
|
|
|
|
//==========================================================
|
|
int j = 0; // 더미...첫번째 라인(헤더 정보) 스킵용
|
|
foreach (var line in allLines) {
|
|
// 맨 첫번째 라인은 텍스트 헤더 정보이므로, 스킵
|
|
if (j == 0) {
|
|
++j;
|
|
continue;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(line)) {
|
|
continue;
|
|
}
|
|
var splits = line.Split(',');
|
|
if (splits.Length < 3) { // 주소, 영문, 국문이 없으면 무효한 라인이므로, 스킵
|
|
continue;
|
|
}
|
|
|
|
// 앞뒤로 불필요한 공백 제거
|
|
var address = splits[0].ToUpper().Trim();
|
|
var descEng = splits[1].Trim();
|
|
var descKor = splits[2].Trim();
|
|
|
|
// 데이터 파일 가독성을 위한 띄어쓰기 처리 추가
|
|
if (string.IsNullOrEmpty(address)) continue;
|
|
|
|
// Comment 정보를 해당 배열에 저장
|
|
int addrNo, addrOffset;
|
|
switch (i) {
|
|
case 0: // B Device Bit Input
|
|
addrNo = int.Parse(address.Substring(1), System.Globalization.NumberStyles.HexNumber);
|
|
addrOffset = addrNo - startAddress;
|
|
BitInputs[addrOffset].description.Comment_EN = descEng;
|
|
BitInputs[addrOffset].description.Comment_KR = descKor;
|
|
break;
|
|
case 1: // B Device Bit Output
|
|
addrNo = int.Parse(address.Substring(1), System.Globalization.NumberStyles.HexNumber);
|
|
addrOffset = addrNo - startAddress;
|
|
BitOutputs[addrOffset].description.Comment_EN = descEng;
|
|
BitOutputs[addrOffset].description.Comment_KR = descKor;
|
|
break;
|
|
case 2: // M Device Bit Input
|
|
addrNo = int.Parse(address.Substring(1), System.Globalization.NumberStyles.Number);
|
|
addrOffset = addrNo - startAddress;
|
|
MBitInputs[addrOffset].description.Comment_EN = descEng;
|
|
MBitInputs[addrOffset].description.Comment_KR = descKor;
|
|
break;
|
|
case 3: // M Device Bit Output
|
|
addrNo = int.Parse(address.Substring(1), System.Globalization.NumberStyles.Number);
|
|
addrOffset = addrNo - startAddress;
|
|
MBitOutputs[addrOffset].description.Comment_EN = descEng;
|
|
MBitOutputs[addrOffset].description.Comment_KR = descKor;
|
|
break;
|
|
case 4: // W Device Word Input
|
|
addrNo = int.Parse(address.Substring(1), System.Globalization.NumberStyles.HexNumber);
|
|
addrOffset = addrNo - startAddress;
|
|
WordInputs[addrOffset].description.Comment_EN = descEng;
|
|
WordInputs[addrOffset].description.Comment_KR = descKor;
|
|
break;
|
|
case 5: // W Device Word Output
|
|
addrNo = int.Parse(address.Substring(1), System.Globalization.NumberStyles.HexNumber);
|
|
addrOffset = addrNo - startAddress;
|
|
WordOutputs[addrOffset].description.Comment_EN = descEng;
|
|
WordOutputs[addrOffset].description.Comment_KR = descKor;
|
|
break;
|
|
case 6: // D Device Word Input
|
|
addrNo = int.Parse(address.Substring(1), System.Globalization.NumberStyles.Number);
|
|
// D는 미리 버퍼를 안 만들었으므로, 줄마다 바로 Add
|
|
DWordInputs.Add(
|
|
(addrNo,
|
|
new AddrCommentList(descEng, descKor))
|
|
);
|
|
break;
|
|
case 7: // D Device Word Output
|
|
addrNo = int.Parse(address.Substring(1), System.Globalization.NumberStyles.Number);
|
|
DWordOutputs.Add(
|
|
(addrNo,
|
|
new AddrCommentList(descEng, descKor))
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
Log.W($"[{DeviceId}] Not exist PLC address file ({strPath[i]})");
|
|
switch (i) {
|
|
case 0:
|
|
case 2: {
|
|
retCode = ePLCErrCode.MemMapPaser_BitInput_LoadFail;
|
|
break;
|
|
}
|
|
case 1:
|
|
case 3: {
|
|
retCode = ePLCErrCode.MemMapPaser_BitOutput_LoadFail;
|
|
break;
|
|
}
|
|
case 4:
|
|
case 6: {
|
|
retCode = ePLCErrCode.MemMapPaser_WordInput_LoadFail;
|
|
break;
|
|
}
|
|
case 5:
|
|
case 7: {
|
|
retCode = ePLCErrCode.MemMapPaser_WordOutput_LoadFail;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 경로에 8개의 파일이 없어도 파싱이 될 수 있도록 수정 (대신 로그 기록 보내기)
|
|
//return false;
|
|
Log.W("Some PLC CSV files cannot be read.");
|
|
}
|
|
}
|
|
Log.I($"[{DeviceId}] PLC address read OK");
|
|
return true;
|
|
} catch (Exception ex) {
|
|
Log.E($"[{DeviceId}] PLC address read error: {ex.Message}");
|
|
switch (index) {
|
|
case 0:
|
|
case 2: {
|
|
retCode = ePLCErrCode.MemMapPaser_BitInput_LoadFail;
|
|
break;
|
|
}
|
|
case 1:
|
|
case 3: {
|
|
retCode = ePLCErrCode.MemMapPaser_BitOutput_LoadFail;
|
|
break;
|
|
}
|
|
case 4:
|
|
case 6: {
|
|
retCode = ePLCErrCode.MemMapPaser_WordInput_LoadFail;
|
|
break;
|
|
}
|
|
case 5:
|
|
case 7: {
|
|
retCode = ePLCErrCode.MemMapPaser_WordOutput_LoadFail;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
} finally {
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 엑셀파일 방식 파서
|
|
protected bool ParsePLCBitWordAddressFromExcel(string bitInputPath, string bitOutputPath, string wordInputPath, string wordOutputPath, out ePLCErrCode retCode)
|
|
{
|
|
retCode = ePLCErrCode.OK;
|
|
|
|
string[] strPath = new string[4];
|
|
string[] strQuery = new string[4];
|
|
|
|
OleDbConnection oleCon = new OleDbConnection();
|
|
System.Data.DataTable dt = new System.Data.DataTable();
|
|
System.Data.DataTable dt1 = new System.Data.DataTable();
|
|
|
|
int index = 0;
|
|
try {
|
|
strPath[0] = BitInputPath;
|
|
strPath[1] = BitOutputPath;
|
|
strPath[2] = MBitInputPath;
|
|
strPath[3] = MBitOutputPath;
|
|
strPath[4] = WordInputPath;
|
|
strPath[5] = WordOutputPath;
|
|
strPath[6] = DWordInputPath;
|
|
strPath[7] = DWordOutputPath;
|
|
// 각 엑셀파일 파싱
|
|
for (int i = 0; i < strPath.Length; i++) {
|
|
index = i;
|
|
if (File.Exists(strPath[i])) {
|
|
string[] strTemp = strPath[i].Split('.');
|
|
if (strTemp[strTemp.Length - 1] == "xlsx") {
|
|
strQuery[i] = string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=\"Excel 12.0;HDR=YES;\"", strPath[i]);
|
|
} else {
|
|
strQuery[i] = string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=Excel 8.0;", strPath[i]);
|
|
}
|
|
oleCon.ConnectionString = strQuery[i];
|
|
oleCon.Open();
|
|
dt = oleCon.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
|
|
|
|
int j = 0;
|
|
// 메모리 주소, CommentEng, CommentKor, Data 인덱스 매칭용
|
|
int indexAddr = 0, indexCommentEng = 0, indexCommentKor = 0, indexData = 0;
|
|
foreach (DataRow row in dt.Rows) {
|
|
string strSheets = row["TABLE_NAME"].ToString().Trim(new[] { '$' });
|
|
|
|
if (strSheets != "UNION") {
|
|
/* // 중요하게 쓰이는 곳이 없어서 주석처리 !!!
|
|
// 각 탭의 메모리 주소를 문자열 배열에 저장
|
|
switch (i) {
|
|
case 0:
|
|
AddrSheet_BitInput[j] = strSheets;
|
|
break;
|
|
case 1:
|
|
AddrSheet_BitOutput[j] = strSheets;
|
|
break;
|
|
case 2:
|
|
AddrSheet_WordInput[j] = strSheets;
|
|
break;
|
|
case 3:
|
|
AddrSheet_WordOutput[j] = strSheets;
|
|
break;
|
|
}
|
|
j++;
|
|
*/
|
|
|
|
OleDbDataAdapter da = new OleDbDataAdapter(string.Format("SELECT * FROM [{0}$]", strSheets), oleCon);
|
|
da.Fill(dt1); // 각 탭의 행 정보 (2행부터 마지막행까지)를 dt1 에 채움 (append 방식)
|
|
|
|
// DBNull 이 중간에 들어가 있는 경우가 있다
|
|
// 따라서, 데이터가 들어가 있는 인덱스를 찾아놔야 함...한번만 찾으면 됨..
|
|
if (j == 0) {
|
|
for (int k = 0; k < dt1.Columns.Count; k++) {
|
|
if (dt1.Columns[k].Caption == "Device No") indexAddr = k;
|
|
if (dt1.Columns[k].Caption == "Comment ENG") indexCommentEng = k;
|
|
if (dt1.Columns[k].Caption == "Comment KOR") indexCommentKor = k;
|
|
if (dt1.Columns[k].Caption == "Data") indexData = k;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
j = 0;
|
|
foreach (DataRow row1 in dt1.Rows) { // 각 장치(B/W, etc)의 모든 행 정보가 dt1.Rows 에 저장되어 있음
|
|
// Comment 정보를 해당 배열에 저장
|
|
switch (i) {
|
|
case 0: // B Device Bit Input
|
|
BitInputs.Add(
|
|
(int.Parse(row1.ItemArray[indexAddr].ToString().Substring(1), System.Globalization.NumberStyles.HexNumber),
|
|
new AddrCommentList(row1.ItemArray[indexCommentEng].ToString(), row1.ItemArray[indexCommentKor].ToString())));
|
|
break;
|
|
case 1: // B Device Bit Output
|
|
BitOutputs.Add(
|
|
(int.Parse(row1.ItemArray[indexAddr].ToString().Substring(1), System.Globalization.NumberStyles.HexNumber),
|
|
new AddrCommentList(row1.ItemArray[indexCommentEng].ToString(), row1.ItemArray[indexCommentKor].ToString())));
|
|
break;
|
|
case 2: // M Device Bit Input
|
|
MBitInputs.Add(
|
|
(int.Parse(row1.ItemArray[indexAddr].ToString().Substring(1), System.Globalization.NumberStyles.Number),
|
|
new AddrCommentList(row1.ItemArray[indexCommentEng].ToString(), row1.ItemArray[indexCommentKor].ToString())));
|
|
break;
|
|
case 3: // M DeviceBit Output
|
|
MBitOutputs.Add(
|
|
(int.Parse(row1.ItemArray[indexAddr].ToString().Substring(1), System.Globalization.NumberStyles.Number),
|
|
new AddrCommentList(row1.ItemArray[indexCommentEng].ToString(), row1.ItemArray[indexCommentKor].ToString())));
|
|
break;
|
|
case 4: // W Device Word Input
|
|
WordInputs.Add(
|
|
(int.Parse(row1.ItemArray[indexAddr].ToString().Substring(1), System.Globalization.NumberStyles.HexNumber),
|
|
new AddrCommentList(row1.ItemArray[indexCommentEng].ToString(), row1.ItemArray[indexCommentKor].ToString())));
|
|
break;
|
|
case 5: // W Device Word Output
|
|
WordOutputs.Add(
|
|
(int.Parse(row1.ItemArray[indexAddr].ToString().Substring(1), System.Globalization.NumberStyles.HexNumber),
|
|
new AddrCommentList(row1.ItemArray[indexCommentEng].ToString(), row1.ItemArray[indexCommentKor].ToString())));
|
|
break;
|
|
case 6: // D Device Word Input
|
|
DWordInputs.Add(
|
|
(int.Parse(row1.ItemArray[indexAddr].ToString().Substring(1), System.Globalization.NumberStyles.Number),
|
|
new AddrCommentList(row1.ItemArray[indexCommentEng].ToString(), row1.ItemArray[indexCommentKor].ToString())));
|
|
break;
|
|
case 7: // D Device Word Output
|
|
DWordOutputs.Add(
|
|
(int.Parse(row1.ItemArray[indexAddr].ToString().Substring(1), System.Globalization.NumberStyles.Number),
|
|
new AddrCommentList(row1.ItemArray[indexCommentEng].ToString(), row1.ItemArray[indexCommentKor].ToString())));
|
|
break;
|
|
}
|
|
++j;
|
|
}
|
|
|
|
dt1.Clear();
|
|
if (oleCon.State == ConnectionState.Open) oleCon.Close();
|
|
|
|
} else {
|
|
Log.W($"[{DeviceId}] Not exist PLC address file ({strPath[i]})");
|
|
switch (i) {
|
|
case 0:
|
|
case 2: {
|
|
retCode = ePLCErrCode.MemMapPaser_BitInput_LoadFail;
|
|
break;
|
|
}
|
|
case 1:
|
|
case 3: {
|
|
retCode = ePLCErrCode.MemMapPaser_BitOutput_LoadFail;
|
|
break;
|
|
}
|
|
case 4:
|
|
case 6: {
|
|
retCode = ePLCErrCode.MemMapPaser_WordInput_LoadFail;
|
|
break;
|
|
}
|
|
case 5:
|
|
case 7: {
|
|
retCode = ePLCErrCode.MemMapPaser_WordOutput_LoadFail;
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
Log.I($"[{DeviceId}] PLC address read OK");
|
|
return true;
|
|
} catch (Exception ex) {
|
|
Log.E($"[{DeviceId}] PLC address read error: {ex.Message}");
|
|
switch (index) {
|
|
case 0:
|
|
case 2: {
|
|
retCode = ePLCErrCode.MemMapPaser_BitInput_LoadFail;
|
|
break;
|
|
}
|
|
case 1:
|
|
case 3: {
|
|
retCode = ePLCErrCode.MemMapPaser_BitOutput_LoadFail;
|
|
break;
|
|
}
|
|
case 4:
|
|
case 6: {
|
|
retCode = ePLCErrCode.MemMapPaser_WordInput_LoadFail;
|
|
break;
|
|
}
|
|
case 5:
|
|
case 7: {
|
|
retCode = ePLCErrCode.MemMapPaser_WordOutput_LoadFail;
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
} finally {
|
|
// Coverity CID 10025
|
|
//if (oleCon != null && oleCon.State != ConnectionState.Closed) oleCon.Close();
|
|
if (oleCon != null) {
|
|
oleCon.Close();
|
|
oleCon.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
#region 세팅/알람 (M, ZR 장치)
|
|
protected struct stAlarmList
|
|
{
|
|
public string DevAddress;
|
|
public string Msg_KR;
|
|
public string Msg_EN;
|
|
public string Msg_CN;
|
|
//public string Station;
|
|
|
|
public bool AlarmOn;
|
|
public Stopwatch OnTime;
|
|
public string StartTime;
|
|
public bool Popup;
|
|
public string Solution_KR;
|
|
public string Solution_EN;
|
|
public string Solution_CN;
|
|
public string Cause_KR;
|
|
public string Cause_EN;
|
|
public string Cause_CN;
|
|
public string ImagePath;
|
|
}
|
|
|
|
protected struct stSettingsList
|
|
{
|
|
public string DevAddress;
|
|
public string Msg_KR;
|
|
public string Msg_EN;
|
|
public string Msg_CN;
|
|
public int nWord;
|
|
public int nDecimals;
|
|
public double Divide;
|
|
public string Unit;
|
|
public double Value;
|
|
}
|
|
|
|
protected bool ParsePLCAlarmSettingAddress(string alarmSettingPath, ref stAlarmList[] alarmList, ref stSettingsList[] settingList, out ePLCErrCode retCode)
|
|
{
|
|
retCode = ePLCErrCode.OK;
|
|
|
|
#region Read Excel Data File
|
|
OleDbConnection oleCon = new OleDbConnection();
|
|
System.Data.DataTable dt = new System.Data.DataTable();
|
|
System.Data.DataTable dt1 = new System.Data.DataTable();
|
|
string strQuery = "";
|
|
|
|
var strPath = alarmSettingPath;
|
|
try {
|
|
if (File.Exists(strPath)) {
|
|
string[] strTemp = strPath.Split('.');
|
|
|
|
if (strTemp[strTemp.Length - 1] == "xlsx") {
|
|
strQuery = string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=\"Excel 12.0;HDR=YES;\"", strPath);
|
|
} else {
|
|
strQuery = string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=Excel 8.0;", strPath);
|
|
}
|
|
|
|
oleCon.ConnectionString = strQuery;
|
|
oleCon.Open();
|
|
dt = oleCon.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
|
|
foreach (DataRow row in dt.Rows) {
|
|
string strSheets = row["TABLE_NAME"].ToString().Trim(new[] { '$' });
|
|
|
|
if (strSheets == "ALARM") { // 알람 탭
|
|
OleDbDataAdapter da = new OleDbDataAdapter(string.Format("SELECT * FROM [{0}$]", strSheets), oleCon);
|
|
da.Fill(dt1); // dt1 에 ALARM 탭의 정보를 채움
|
|
Array.Resize(ref alarmList, dt1.Rows.Count); // dt1 의 행의 개수만큼 _stAlarmList 의 크기 늘리기
|
|
// stEventsList 구조체 필드 관련
|
|
// 엑셀 Column 에 대한 index 를 의미함
|
|
int iCode = 0;
|
|
int iName_Kr = 0;
|
|
int iName_En = 0;
|
|
int iName_Cn = 0;
|
|
|
|
int iPopup = 0;
|
|
int iSolution_KR = 0;
|
|
int iSolution_EN = 0;
|
|
int iSolution_CN = 0;
|
|
int iCause_KR = 0;
|
|
int iCause_EN = 0;
|
|
int iCause_CN = 0;
|
|
int iImagePath = 0;
|
|
|
|
// ALARM 탭의 첫 행의 열 정보가 Columns 에 배열로 저장되어 있음
|
|
for (int i = 0; i < dt1.Columns.Count; i++) { // Column 인덱스 매핑 ??
|
|
if (dt1.Columns[i].Caption == "Address") iCode = i;
|
|
if (dt1.Columns[i].Caption == "DESCRIPTION 한글") iName_Kr = i;
|
|
if (dt1.Columns[i].Caption == "DESCRIPTION 영문") iName_En = i;
|
|
if (dt1.Columns[i].Caption == "DESCRIPTION 중문") iName_Cn = i;
|
|
|
|
if (dt1.Columns[i].Caption == "POPUP") iPopup = i;
|
|
if (dt1.Columns[i].Caption == "발생원인 한글") iCause_KR = i;
|
|
if (dt1.Columns[i].Caption == "발생원인 영문") iCause_EN = i;
|
|
if (dt1.Columns[i].Caption == "발생원인 중문") iCause_CN = i;
|
|
if (dt1.Columns[i].Caption == "조치방법 한글") iSolution_KR = i;
|
|
if (dt1.Columns[i].Caption == "조치방법 영문") iSolution_EN = i;
|
|
if (dt1.Columns[i].Caption == "조치방법 중문") iSolution_CN = i;
|
|
if (dt1.Columns[i].Caption == "IMAGE") iImagePath = i;
|
|
}
|
|
|
|
int index = 0;
|
|
foreach (DataRow row1 in dt1.Rows) { // dt1.Rows 는 ALARM 탭의 2번째 행부터 데이터가 저장되어 있음
|
|
if (row1.ItemArray[0].ToString().Length > 0) { // 각 행의 첫번째 열의 정보
|
|
alarmList[index].AlarmOn = false;
|
|
alarmList[index].DevAddress = row1.ItemArray[iCode].ToString(); // Address 정보
|
|
alarmList[index].Msg_KR = row1.ItemArray[iName_Kr].ToString(); // DESCRIPTION 한글 정보
|
|
alarmList[index].Msg_EN = row1.ItemArray[iName_En].ToString(); // DESCRIPTION 영문 정보
|
|
alarmList[index].Msg_CN = row1.ItemArray[iName_Cn].ToString(); // DESCRIPTION 중문 정보
|
|
alarmList[index].OnTime = new Stopwatch();
|
|
alarmList[index].StartTime = "";
|
|
|
|
alarmList[index].Popup = (row1.ItemArray[iPopup].ToString() == "1");
|
|
alarmList[index].Solution_KR = row1.ItemArray[iSolution_KR].ToString();
|
|
alarmList[index].Solution_EN = row1.ItemArray[iSolution_EN].ToString();
|
|
alarmList[index].Solution_CN = row1.ItemArray[iSolution_CN].ToString();
|
|
alarmList[index].Cause_KR = row1.ItemArray[iCause_KR].ToString();
|
|
alarmList[index].Cause_EN = row1.ItemArray[iCause_EN].ToString();
|
|
alarmList[index].Cause_CN = row1.ItemArray[iCause_CN].ToString();
|
|
alarmList[index].ImagePath = row1.ItemArray[iImagePath].ToString();
|
|
index++;
|
|
} else { // 첫번째 열정보가 비어있는 경우, 예외처리인가 ??
|
|
alarmList[index].AlarmOn = false;
|
|
alarmList[index].DevAddress = "";
|
|
alarmList[index].Msg_KR = "";
|
|
alarmList[index].Msg_EN = "";
|
|
alarmList[index].Msg_CN = "";
|
|
alarmList[index].OnTime = new Stopwatch();
|
|
alarmList[index].StartTime = "";
|
|
|
|
alarmList[index].Popup = false;
|
|
alarmList[index].Solution_KR = "";
|
|
alarmList[index].Solution_EN = "";
|
|
alarmList[index].Solution_CN = "";
|
|
alarmList[index].Cause_KR = "";
|
|
alarmList[index].Cause_EN = "";
|
|
alarmList[index].Cause_CN = "";
|
|
alarmList[index].ImagePath = "";
|
|
index++;
|
|
}
|
|
}
|
|
dt1.Clear();
|
|
}
|
|
|
|
if (strSheets == "SETTING") { // 세팅 탭
|
|
OleDbDataAdapter da = new OleDbDataAdapter(string.Format("SELECT * FROM [{0}$]", strSheets), oleCon);
|
|
da.Fill(dt1);
|
|
Array.Resize(ref settingList, dt1.Rows.Count);
|
|
// 엑셀 Column 에 대한 index 를 의미함
|
|
int iCode = 0;
|
|
int iName_Kr = 0;
|
|
int iName_En = 0;
|
|
int iName_Cn = 0;
|
|
int iWord = 0;
|
|
int iDecimal = 0;
|
|
int iUnit = 0;
|
|
int iDivide = 0;
|
|
|
|
for (int i = 0; i < dt1.Columns.Count; i++) {
|
|
if (dt1.Columns[i].Caption == "Address") iCode = i;
|
|
if (dt1.Columns[i].Caption == "DESCRIPTION 한글") iName_Kr = i;
|
|
if (dt1.Columns[i].Caption == "DESCRIPTION 영문") iName_En = i;
|
|
if (dt1.Columns[i].Caption == "DESCRIPTION 중문") iName_Cn = i;
|
|
if (dt1.Columns[i].Caption == "Word Size") iWord = i;
|
|
if (dt1.Columns[i].Caption == "Decimal Place(소수점)") iDecimal = i;
|
|
if (dt1.Columns[i].Caption == "기준 단위") iUnit = i;
|
|
if (dt1.Columns[i].Caption == "Divide") iDivide = i;
|
|
}
|
|
|
|
int index = 0;
|
|
foreach (DataRow row1 in dt1.Rows) {
|
|
if (row1.ItemArray[0].ToString().Length > 0) { // 각 행의 첫번째 열의 정보
|
|
settingList[index].DevAddress = row1.ItemArray[iCode].ToString();
|
|
settingList[index].Msg_KR = row1.ItemArray[iName_Kr].ToString();
|
|
settingList[index].Msg_EN = row1.ItemArray[iName_En].ToString();
|
|
settingList[index].Msg_CN = row1.ItemArray[iName_Cn].ToString();
|
|
settingList[index].Unit = row1.ItemArray[iUnit].ToString();
|
|
int.TryParse(row1.ItemArray[iWord].ToString(), out settingList[index].nWord);
|
|
int.TryParse(row1.ItemArray[iDecimal].ToString(), out settingList[index].nDecimals);
|
|
double.TryParse(row1.ItemArray[iDivide].ToString(), out settingList[index].Divide);
|
|
if (settingList[index].Divide == 0) settingList[index].Divide = 1;
|
|
index++;
|
|
} else {
|
|
settingList[index].DevAddress = "";
|
|
settingList[index].Msg_KR = "";
|
|
settingList[index].Msg_EN = "";
|
|
settingList[index].Msg_CN = "";
|
|
settingList[index].Unit = "";
|
|
|
|
settingList[index].nWord = 1;
|
|
settingList[index].Divide = 1;
|
|
settingList[index].nDecimals = 0;
|
|
index++;
|
|
}
|
|
}
|
|
dt1.Clear();
|
|
}
|
|
}
|
|
//if (oleCon.State == ConnectionState.Open) oleCon.Close();
|
|
return true;
|
|
} else { // Not Exist
|
|
Log.E($"Not exist: {strPath}");
|
|
retCode = ePLCErrCode.MemMapPaser_SettingAlarm_LoadFail;
|
|
}
|
|
return false;
|
|
} catch (Exception ex) {
|
|
Log.E($"Perhaps Microsoft Access database engine 64bit problem: {ex.Message}");
|
|
retCode = ePLCErrCode.MemMapPaser_SettingAlarm_LoadFail;
|
|
return false;
|
|
} finally {
|
|
// Coverity CID 10024
|
|
//if (oleCon != null && oleCon.State != ConnectionState.Closed) oleCon.Close();
|
|
if (oleCon != null) {
|
|
oleCon.Close();
|
|
oleCon.Dispose();
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
#endregion
|
|
#endregion
|
|
}
|
|
}
|