본문으로 건너뛰기

LS XGI / XBC / XBM (FEnet) 드라이버

개요

LS ELECTRIC (구 LS산전) 의 XGI / XBC / XBM PLC 시리즈 와 FEnet (Fast Ethernet) 표준 프로토콜로 통신합니다.

항목
opc_typeLS
구현 클래스plantpulse.driver.protocol.ls.LSDriver
통신 라이브러리자체 자바 구현 (plantpulse-plc-driver-ls.jarFEnetClient)
read
write❌ (자체 자바 드라이버에 write API 미노출)
보안없음 (사내망 전제)
네이티브 → Java 전환

이전엔 .NET 으로 작성된 외부 네이티브 바이너리 (FENetClient.exe) 를 외부 프로세스로 띄워 쓰는 NativeProcessDriver 기반이었습니다. 2025 부터 자체 자바 구현 (plantpulse-plc-driver-ls.jar) 으로 전환 되어 socket 직접 통신을 합니다. 결과적으로 OS 의존 / 외부 프로세스 관리 부담이 사라졌습니다.


OPC 등록 폼

필드의미예시
opc_agent_ipPLC IP192.168.0.80
opc_agent_portFEnet 포트2004 (XGT 기본)
timecycle폴링 주기 (ms)1000
options.company-idFEnet company ID (선택)(보통 비움)
options.use-checksumBCC 체크섬 사용false (default)
options.use-hex-bit-index비트 index 16진 사용false (default)
options.connect-timeout연결 타임아웃 (ms)3000
options.read-timeoutread 타임아웃 (ms)3000

connect 단계에서 NetUtils.isReachable(ip) ping 검사를 먼저 수행합니다 → 도달 불가 시 즉시 실패.


태그 plc_address 형식

FEnet 표준 (정식 형식)

%<DeviceType><DataType><Index>
예시의미
%DW00309Data 영역, Word, index 309
%DD00600Data 영역, Double Word (32-bit), index 600
%DL00800Data 영역, Long Word (64-bit), index 800
%MX02704Memory 영역, Bit, index 0x2704 (또는 10진 2704)
%MW00100Memory 영역, Word, index 100

DeviceType 글자: D (Data), M (Memory), K (Keep), F (Flag), T (Timer), C (Counter), R 등 LS XGI 의 표준 디바이스 코드.

DataType 글자: X (Bit), B (Byte), W (Word, 16-bit), D (Double Word, 32-bit), L (Long Word, 64-bit).

Edge short form (권장)

Edge 는 short form 을 받아 자동으로 FEnet 표준으로 변환합니다.

Edge 입력data_typeformat자동 변환 결과
D00309Integer(empty)%DW00309
D00600IntegerDW%DD00600
D00800Long(empty)%DL00800
M02704Boolean(empty)%MX02704
D00100FloatREAL (또는 default)%DD00100
D00200DoubleLREAL%DL00200
D00300StringSTR[10]%DW00300 부터 10 워드 read

내부 구현 (LSDriver.toFEnet):

// raw plc_address (D00309) → %D<dataCh>00309
static String toFEnet(String rawAddr, char dataCh) {
if (rawAddr.charAt(0) == '%') return rawAddr; // 이미 표준이면 유지
return "%" + Character.toUpperCase(rawAddr.charAt(0)) + dataCh + rawAddr.substring(1);
}

dataChdata_typeformat 으로 결정 (다음 절 참고).


data_type / format 매핑 (상세)

LS 드라이버는 같은 short address 로도 format 만으로 메모리 폭을 명시 변경 할 수 있어, 한 디바이스 디바이스 코드를 16/32/64-bit 로 자유롭게 매핑할 수 있습니다.

Integer 계열 (data_type=Integer / Short / Int / Int16 …)

format의미FEnet DataType호출
(empty)Word (signed)16-bit%DWclient.readWord
UI / UW / UWORDUWord (unsigned)16-bit%DWclient.readUWord
DW / DWORD / DOUBLE_WORDDWord (signed)32-bit%DDclient.readDWord
UL / UDW / DUW / UDWORDUDWord (unsigned)32-bit%DDclient.readUDWord
LW / LWORD / LONG_WORDLWord (signed)64-bit%DLclient.readLWord
ULW / ULWORDULWord (unsigned)64-bit%DLclient.readULWord
format 미지정의 의미

D00309 + data_type=Integer + format= (비움) 은 16-bit signed Word 입니다. 32-bit 가 필요하면 format=DW 로 명시. 이는 LS XGI 의 일반 컨벤션과 일치합니다.

Float / Double

data_typeformatFEnet호출
Float / REAL(empty) / REAL / FLOAT32-bit IEEE 754%DDclient.readFloat
Double / LREAL(empty) / LREAL / DOUBLE64-bit IEEE 754%DLclient.readDouble

format=DW + data_type=Float 인 경우에도 32-bit float 으로 해석합니다 (isFloat(dt) 분기).

Boolean

data_typeformat의미
Boolean / Bool / Bit(empty) / X / BIT%MX... 또는 %PX... 비트 단위 read

short form 그대로: M02704%MX02704.

String

format의미
STR1 워드 (2 글자)16-bit × 1
STR[N]N 워드 (2N 글자)16-bit × N

ASCII 인코딩 + low byte first (LS spec). 끝의 NUL 바이트는 trim 처리.

// readStringMulti: N 워드 연속 read, low byte 먼저
for (int i = 0; i < wordCount; i++) {
int w = client.readUWord("%" + dev + "W" + (startIdx + i));
bytes[b++] = (byte) (w & 0xFF);
bytes[b++] = (byte) ((w >> 8) & 0xFF);
}

BIN / BIN[idx]

워드 1개를 읽고 비트 idx 만 추출 (Modbus 와 같은 컨벤션).

format동작
BIN워드 read 후 bit 0 추출
BIN[5]bit 5 추출
BIN[F]hex F (=15) bit 추출 (한 글자는 16진 허용)

흔한 에러 + 해결

메시지 / 증상원인해결
LS-PLC Ping failed : <ip>ICMP 비도달케이블/방화벽 확인. ICMP block 환경이면 차단 정책 검토
LS connect failedFEnet 모듈 미가동 / 포트 mismatchXG5000 에서 FEnet 모듈 IP/Port 확인 (보통 2004)
값이 항상 0short form → 폭 mismatch (Word 인데 DWord 로 읽음)format 비우거나 DW 로 정확히 명시
32-bit float 깨짐format 누락 → Word 로 읽힘data_type=Float + format=REAL 명시
문자열 글자가 깨짐LS 의 low-byte-first 미반영edge 가 알아서 처리. 외부에서 bytes 직접 해석하면 주의
Bit 가 안 읽힘short form 이 D00100 으로 들어감M02704 (M 영역) 사용 또는 format=BIN[idx]

curl 등록 예시

XGI / XBC / XBM 의 D, M 영역 혼합 예:

curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_LS_XBM_0001",
"opc_type": "LS",
"opc_name": "XBM Line A",
"opc_agent_ip": "192.168.0.80",
"opc_agent_port": "2004",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"options": {
"connect-timeout": "3000",
"read-timeout": "3000",
"use-checksum": "false"
},
"tag_list": [
{
"tag_id": "OPC_LS_XBM_0001_TAG_00001",
"tag_name": "Counter",
"plc_address": "D00309",
"data_type": "Integer"
},
{
"tag_id": "OPC_LS_XBM_0001_TAG_00002",
"tag_name": "Production32",
"plc_address": "D00600",
"data_type": "Integer",
"format": "DW"
},
{
"tag_id": "OPC_LS_XBM_0001_TAG_00003",
"tag_name": "Pressure",
"plc_address": "D00100",
"data_type": "Float",
"format": "REAL"
},
{
"tag_id": "OPC_LS_XBM_0001_TAG_00004",
"tag_name": "Status",
"plc_address": "M02704",
"data_type": "Boolean"
},
{
"tag_id": "OPC_LS_XBM_0001_TAG_00005",
"tag_name": "BatchName",
"plc_address": "D00500",
"data_type": "String",
"format": "STR[10]"
},
{
"tag_id": "OPC_LS_XBM_0001_TAG_00006",
"tag_name": "Bit3OfStatus",
"plc_address": "D00400",
"data_type": "Integer",
"format": "BIN[3]"
}
]
}'

값 read:

curl -s http://<edge-host>/api/v1/tag/OPC_LS_XBM_0001_TAG_00003/value | jq

동작 메모

  • Read 분기 우선순위 (LSDriver.read()):
    1. data_type=String 또는 formatSTR / STR[N]readStringMulti
    2. formatBIN 또는 BIN[idx] → 워드 read 후 비트 추출
    3. format 명시 분기 (UI, DW, UL, LW, REAL, LREAL 등) → readByFormat
    4. 폴백: data_type 단독 (readByDataType)
  • client 호출은 synchronized 되어 있어 한 OPC 의 다중 태그 read 는 직렬. 다른 OPC 는 별 스레드.
  • write 는 미구현. Sparkplug NCMD 를 사용하려면 후속에서 FEnetClient.write* 노출 필요.

예제 모음 (데이터타입별)

LS XGI / XBC / XBM 운영 현장에서 자주 쓰는 모든 조합을 한 표에 정리. plc_address 는 short form 기준이며 자동으로 %D... 표준 표기로 변환됩니다.

비트 / 불 (Boolean)

data_typeformatplc_address메모리 폭의미 / 비고
Boolean(empty)M027041 bit메모리 영역 비트. 가장 흔한 형태
Boolean(empty)P0001F1 bitLS XGI 16진 비트 인덱스 (F=15). use-hex-bit-index=true 일 때
Boolean(empty)K000101 bitKeep relay 비트
Boolean(empty)T01101 bitTimer 출력 비트
BooleanBIND0030916 bit → bit 0워드 read 후 0 비트 추출
BooleanBIN[3]D0030916 bit → bit 3bit 3 추출
BooleanBIN[F]D0030916 bit → bit 15hex 인덱스 F=15

16-bit 정수 (Integer/Word)

data_typeformatplc_address메모리 폭의미
Integer(empty)D0030916 bitsigned Word (%DW00309)
IntegerUID0030016 bitUWord (unsigned, client.readUWord)
IntegerUWORDD0030016 bitUI 와 동일

32-bit 정수 (DWord)

data_typeformatplc_address메모리 폭의미
IntegerDWD0060032 bitDWord signed (%DD00600, D00600/00601 결합)
IntegerDWORDD0060032 bit위와 동일
IntegerDUWD0069032 bitUDWord (unsigned 32)
IntegerUDWD0069032 bitUDW 와 동일
IntegerULD0069032 bitULong = UDWord (alias)

64-bit 정수 (LWord)

data_typeformatplc_address메모리 폭의미
Long(empty)D0070064 bitLWord signed (%DL00700)
LongLWD0070064 bit명시 LWord
LongLWORDD0070064 bit동일
LongULWD0075064 bitULWord (unsigned 64)

실수 (Float / Double)

data_typeformatplc_address메모리 폭의미
Float(empty)D0045032 bitIEEE 754 single (%DD00450)
FloatREALD0045032 bit명시
FloatDWD0045032 bitisFloat(dt) 분기로 float 처리
DoubleLREALD0050064 bitIEEE 754 double (%DL00500)
Double(empty)D0050064 bitLREAL 과 동일

문자열 (String)

data_typeformatplc_address메모리 폭의미
StringSTRD0080316 bit (1 word)2 chars (low byte first)
StringSTR[5]D0080080 bit (5 words)10 chars, NUL trim
StringSTR[10]D00500160 bit (10 words)20 chars
StringSTR[16]D0010032 word32 chars (32 byte) — XGI 권장 정렬

Formula 활용 (LS 현장 패턴)

용도data_typefomula입력 → 출력
정수 raw → 소수 1자리Float${VALUE}*0.11638163.8
정수 raw → 소수 2자리Float${VALUE}*0.0112345123.45
signed 16 → unsigned 변환Integer${VALUE}+65536음수 raw 보정 (16-bit 환산)
영점 보정 (다른 태그)Float${VALUE}-${TAG_ZERO}1024 - 24 = 1000
캘리브 (gain × x + offset)Float${VALUE}*${TAG_GAIN}+${TAG_OFFSET}보정 계수 별도 태그
Hz 변환 (rpm → Hz)Float${VALUE}/60180030.0

자세한 fomula 사용은 Formula 레퍼런스 참고.

curl 종합 예제 (모든 타입 한 번에)

curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_LS_FULL",
"opc_type": "LS",
"opc_name": "LS XGI Full",
"opc_agent_ip": "192.168.0.80",
"opc_agent_port": "2004",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"options": { "use-hex-bit-index": "true" },
"tag_list": [
{"tag_id":"OPC_LS_FULL_T01", "tag_name":"BitM", "plc_address":"M02704", "data_type":"Boolean"},
{"tag_id":"OPC_LS_FULL_T02", "tag_name":"BitPHex", "plc_address":"P0001F", "data_type":"Boolean"},
{"tag_id":"OPC_LS_FULL_T03", "tag_name":"WordSigned", "plc_address":"D00309", "data_type":"Integer"},
{"tag_id":"OPC_LS_FULL_T04", "tag_name":"WordU", "plc_address":"D00300", "data_type":"Integer", "format":"UI"},
{"tag_id":"OPC_LS_FULL_T05", "tag_name":"DWordS", "plc_address":"D00600", "data_type":"Integer", "format":"DW"},
{"tag_id":"OPC_LS_FULL_T06", "tag_name":"DWordU", "plc_address":"D00690", "data_type":"Integer", "format":"DUW"},
{"tag_id":"OPC_LS_FULL_T07", "tag_name":"LWord", "plc_address":"D00700", "data_type":"Long"},
{"tag_id":"OPC_LS_FULL_T08", "tag_name":"Real", "plc_address":"D00450", "data_type":"Float", "format":"REAL"},
{"tag_id":"OPC_LS_FULL_T09", "tag_name":"LReal", "plc_address":"D00500", "data_type":"Double", "format":"LREAL"},
{"tag_id":"OPC_LS_FULL_T10", "tag_name":"Str1Word", "plc_address":"D00803", "data_type":"String", "format":"STR"},
{"tag_id":"OPC_LS_FULL_T11", "tag_name":"Str10Word", "plc_address":"D00500", "data_type":"String", "format":"STR[10]"},
{"tag_id":"OPC_LS_FULL_T12", "tag_name":"BitOfWord", "plc_address":"D00309", "data_type":"Boolean", "format":"BIN[3]"},
{"tag_id":"OPC_LS_FULL_T13", "tag_name":"PressScale", "plc_address":"D00100", "data_type":"Float", "format":"REAL", "fomula":"${VALUE}*0.1"}
]
}'