이전글에서 XGB와 XGT 차이점, 그리고 대체 내 소켓프로그램은 왜 쓰레기값을 들고 오는가에 대해 잠깐 살펴봤다.
뭐 쓰레기값이던 말든 뭔가 들고 오는 거 자체가 안 되는 사람도 있을 테니까 그 사람들을 위한 정보부터 소개한다.
대부분의 PLC 소켓통신이 그렇듯 소켓에 헤더를 달아서 보내줘야 PLC도 헤더를 참조해서 리시브를 던져준다. (근데 이놈은 왜 막 주고 난리..)
헤더 타입에는 총 네구간이 있는데 중요한 건 'Company Header'와 'Data Type', 'Data' 세 가지이다. 쓰다 보니 다 중요하네?
Company Header에는 통신하고자 하는 PLC 정보가 포함된다. Data Type과 Data는 이전 문서에서도 설명 했듯 연속으로 읽어올 경우 Byte로 구현해야 하기 때문에 이에 맞는 데이터 길이를 설정해서 보내줘야 정상적인 답변이 들어온다.
그렇다면 헤더를 풀어헤쳐보자. 솔직히 개발하다 안되서 이 문서를 찾았을 거라 보고 잡다한 설명은 패스하겠다.
대충 배열로 헤더구성은 배껴도 상관없다.
다만 중요한 문구들은 주석에 '// 어드레스 주소 개수에 영향'이라고 적어놓았다.
어드레스 주소 자릿수에 따라 수치나 배열 개수를 조정해줘야 하는 부분들이다.
어드레스 자릿수가 늘어나거나 줄어들면 18번 라인과 31번 라인의 변수 수치를 조정해 준다. 그리고 34 ~ 42 라인에 있는 변수의 개수를 조정해 주면 된다.
예를 들면 %DB1000 이면 총 7 자릿수이기 때문에 배열을 0 ~ 6까지 구성하고 배열의 수치를 앞으로 당겨 조정해 주면 된다.
물론 7자리수로 변경된다고 하면 배열 끝부분 인덱스가 sendData[38]이기 때문에 총 배열 길이를 줄여주어야 한다.
(byte[] sendData = new byte[41];)
왜 바이트인가요?라고 물으신다면, 연속 읽기 하려고 그런다. 연속 읽기란 Start Address를 주고 읽어야 하는 카운트를 지정해서 요청을 보내면 해당 길이에 맞게 ';'로 구분된 문자열을 보내주는 기능이다. 웬만한 PLC에 다 있으니 유용하게 쓰자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
//---읽기 헤더
sendData[0] = Convert.ToByte('L');//0x4C
sendData[1] = Convert.ToByte('S');//0x53
sendData[2] = Convert.ToByte('I');//0x49
sendData[3] = Convert.ToByte('S');//0x53
sendData[4] = Convert.ToByte('-');//0x2D
sendData[5] = Convert.ToByte('X');//0x58
sendData[6] = Convert.ToByte('G');//0x47
sendData[7] = Convert.ToByte('T');//0x54
sendData[8] = 0x00;//예약(0x0000)
sendData[9] = 0x00;
sendData[10] = 0x00;//PLC_INFO(0x0000)
sendData[11] = 0x00;
sendData[12] = 0x00;//CPU_INFO(0xB0)(로라반PLC CPU Type)
sendData[13] = 0x33;//소스프레임(0x33)
sendData[14] = 0x00;//INVOKE_ID(0x0000)
sendData[15] = 0x00;
sendData[16] = 0x15;//LENGTH(0x0014) 어드레스 주소 개수에 영향
sendData[17] = 0x00;
sendData[18] = 0x00;//FENET_POSITION(0x00)
sendData[19] = 0x14;//예약영역(0x00)헤더바이트수
//--명령
sendData[20] = 0x54;//명령(0x54)
sendData[21] = 0x00;
sendData[22] = 0x14;//연속
sendData[23] = 0x00;
sendData[24] = 0x00;//예약(0x0000)
sendData[25] = 0x00;
sendData[26] = 0x01;//읽을변수개수(0x0001)
sendData[27] = 0x00;
sendData[28] = 0x09;//변수길이(0x0008):(%DW00016)// 어드레스 주소 개수에 영향
sendData[29] = 0x00;
byte[] plcAddr = Encoding.UTF8.GetBytes(STR_ADDR);//PLC읽기시작주소
sendData[30] = plcAddr[0];//(%DB000001)
sendData[31] = plcAddr[1];
sendData[32] = plcAddr[2];
sendData[33] = plcAddr[3];
sendData[34] = plcAddr[4];
sendData[35] = plcAddr[5];
sendData[36] = plcAddr[6];
sendData[37] = plcAddr[7];
sendData[38] = plcAddr[8];// 어드레스 주소 개수에 영향
string cntString = string.Format("{0:X4}", 2 * addrCnt);
sendData[39] = Convert.ToByte(cntString.Substring(2, 2), 16);//Hexa String(cntBytes)하위바이트
sendData[40] = Convert.ToByte(cntString.Substring(0, 2), 16);//Hexa String(cntBytes)상위바이트
|
cs |
쓰기 헤더도 읽기 헤더와 마찬가지로 확인해 볼 수 있다. '// 어드레스 주소 개수에 영향' 주석을 유심히 보면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
//---쓰기 헤더
//--Application Header(20바이트)
sendData[0] = Convert.ToByte('L');
sendData[1] = Convert.ToByte('S');
sendData[2] = Convert.ToByte('I');
sendData[3] = Convert.ToByte('S');
sendData[4] = Convert.ToByte('-');
sendData[5] = Convert.ToByte('X');
sendData[6] = Convert.ToByte('G');
sendData[7] = Convert.ToByte('T');
sendData[8] = 0x00;//예약영역(0x0000) 상하위바이트위치교환(항목 및 데이터값 전체 적용됨)
sendData[9] = 0x00;
sendData[10] = 0x00;//PLC_INFO(0x0000) PC->PLC전송시(0x0000)
sendData[11] = 0x00;
sendData[12] = 0x00;//CPU_INFO(0x00)
sendData[13] = 0x33;//소스프레임(0x33) PC->PLC(0x33), PLC->PC(0x11)
sendData[14] = 0x00;//INVOKE_ID(0x0000) 프레임순서지정
sendData[15] = 0x00;
string hexString = string.Format("{0:X4}", 20 + 2 * addrCnt); // 어드레스 주소 개수에 영향 '20'변경 필요
sendData[16] = Convert.ToByte(hexString.Substring(2, 2), 16);//Hexa String(hexString)하위바이트
sendData[17] = Convert.ToByte(hexString.Substring(0, 2), 16);//Hexa String(hexString)상위바이트
sendData[18] = 0x00;//FENET_POSITION(0x00)
sendData[19] = 0x14;//예약영역(0x00), BCC:App Header Byte수(0x14)
sendData[20] = 0x58;//바이트연속쓰기명령(0x0058)
sendData[21] = 0x00;
sendData[22] = 0x14;//데이터타입(0x0000):연속(블록)
sendData[23] = 0x00;
sendData[24] = 0x00;//예약영역(0x0000)
sendData[25] = 0x00;
sendData[26] = 0x01;//변수개수(0x0001):PLC주소개수(연속:0x0001-쓰기시작위치)
sendData[27] = 0x00;
sendData[28] = 0x08;//변수길이(0x0007):PLC주소구성 바이트수 // 어드레스 주소 개수에 영향
sendData[29] = 0x00;
byte[] plcAddr = Encoding.UTF8.GetBytes(STR_ADDR);
sendData[30] = plcAddr[0];//(%DB000016) PLC 블록시작주소
sendData[31] = plcAddr[1];
sendData[32] = plcAddr[2];
sendData[33] = plcAddr[3];
sendData[34] = plcAddr[4];
sendData[35] = plcAddr[5];
sendData[36] = plcAddr[6];
sendData[37] = plcAddr[7];
sendData[38] = plcAddr[8];
string cntString = string.Format("{0:X4}", 2 * addrCnt);
sendData[39] = Convert.ToByte(cntString.Substring(2, 2), 16);//Hexa String(cntBytes)하위바이트
sendData[40] = Convert.ToByte(cntString.Substring(0, 2), 16);//Hexa String(cntBytes)상위바이트
|
cs |
자 지금까지 헤더에 대해 찍어먹어 봤다.
내부 분석을 하면 좀 더 다양한 메시지를 주고받을 수 있을 것 같지만 어차피 개발자들이 필요한 건 신호 주고받거나 결과 데이터 주고받는 정도니 잡다한 건 알아서 공부해서 공유해 주길 바란다.
그런데 헤더만 구성해서 소켓을 던진다고 PLC에서 알아먹을 수 있는 건 아니다. 대부분의 PLC가 그렇듯 워드를 쪼개는데 15 ~ 0까지 구성한다. 우리가 스트링을 주르륵 쓰면 0 ~ 15로 입력이 되니 이걸 반전해줘야 한다.
다음 포스팅에서는 반전하는 방법과 최종적으로 데이터를 읽어오는 시퀀스에 대해 알아보겠다.