LS XGI / XBC / XBM (FEnet) Driver
Overview
Communicates with LS ELECTRIC (formerly LSIS) XGI / XBC / XBM PLC series using the FEnet (Fast Ethernet) standard protocol.
| Item | Value |
|---|---|
opc_type | LS |
| Implementation class | plantpulse.driver.protocol.ls.LSDriver |
| Communication library | In-house Java implementation (plantpulse-plc-driver-ls.jar → FEnetClient) |
| read | ✅ |
| write | ❌ (no write API exposed in the in-house Java driver) |
| Security | None (assumes internal network) |
Previously this was based on NativeProcessDriver, which launched an external native binary (FENetClient.exe) written in .NET as a separate process. Since 2025 it has been migrated to an in-house Java implementation (plantpulse-plc-driver-ls.jar) that communicates directly over sockets. As a result, the OS dependency and the burden of managing an external process are gone.
OPC Registration Form
| Field | Meaning | Example |
|---|---|---|
opc_agent_ip | PLC IP | 192.168.0.80 |
opc_agent_port | FEnet port | 2004 (XGT default) |
timecycle | Polling interval (ms) | 1000 |
options.company-id | FEnet company ID (optional) | (usually left empty) |
options.use-checksum | Use BCC checksum | false (default) |
options.use-hex-bit-index | Use hexadecimal bit index | false (default) |
options.connect-timeout | Connection timeout (ms) | 3000 |
options.read-timeout | Read timeout (ms) | 3000 |
During the connect stage a NetUtils.isReachable(ip) ping check is performed first → immediate failure if unreachable.
Tag plc_address Format
FEnet standard (formal notation)
%<DeviceType><DataType><Index>
| Example | Meaning |
|---|---|
%DW00309 | Data area, Word, index 309 |
%DD00600 | Data area, Double Word (32-bit), index 600 |
%DL00800 | Data area, Long Word (64-bit), index 800 |
%MX02704 | Memory area, Bit, index 0x2704 (or decimal 2704) |
%MW00100 | Memory area, Word, index 100 |
DeviceType letters: D (Data), M (Memory), K (Keep), F (Flag), T (Timer), C (Counter), R, and other standard LS XGI device codes.
DataType letters: X (Bit), B (Byte), W (Word, 16-bit), D (Double Word, 32-bit), L (Long Word, 64-bit).
Edge short form (recommended)
Edge accepts the short form and converts it automatically to FEnet standard notation.
| Edge input | data_type | format | Automatic conversion result |
|---|---|---|---|
D00309 | Integer | (empty) | %DW00309 |
D00600 | Integer | DW | %DD00600 |
D00800 | Long | (empty) | %DL00800 |
M02704 | Boolean | (empty) | %MX02704 |
D00100 | Float | REAL (or default) | %DD00100 |
D00200 | Double | LREAL | %DL00200 |
D00300 | String | STR[10] | 10-word read starting at %DW00300 |
Internal implementation (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);
}
dataCh is determined by data_type and format (see the next section).
data_type / format Mapping (details)
The LS driver lets you explicitly change the memory width using format alone, even for the same short address, so a single device code can be freely mapped as 16/32/64-bit.
Integer family (data_type=Integer / Short / Int / Int16 …)
format | Meaning | Width | FEnet DataType | Call |
|---|---|---|---|---|
| (empty) | Word (signed) | 16-bit | %DW | client.readWord |
UI / UW / UWORD | UWord (unsigned) | 16-bit | %DW | client.readUWord |
DW / DWORD / DOUBLE_WORD | DWord (signed) | 32-bit | %DD | client.readDWord |
UL / UDW / DUW / UDWORD | UDWord (unsigned) | 32-bit | %DD | client.readUDWord |
LW / LWORD / LONG_WORD | LWord (signed) | 64-bit | %DL | client.readLWord |
ULW / ULWORD | ULWord (unsigned) | 64-bit | %DL | client.readULWord |
D00309 + data_type=Integer + format= (empty) means 16-bit signed Word.
If you need 32-bit, specify format=DW explicitly. This matches the usual LS XGI convention.
Float / Double
data_type | format | Width | FEnet | Call |
|---|---|---|---|---|
Float / REAL | (empty) / REAL / FLOAT | 32-bit IEEE 754 | %DD | client.readFloat |
Double / LREAL | (empty) / LREAL / DOUBLE | 64-bit IEEE 754 | %DL | client.readDouble |
Even with format=DW + data_type=Float, the value is interpreted as a 32-bit float (isFloat(dt) branch).
Boolean
data_type | format | Meaning |
|---|---|---|
Boolean / Bool / Bit | (empty) / X / BIT | Bit-level read of %MX... or %PX... |
Short form as-is: M02704 → %MX02704.
String
format | Meaning | Width |
|---|---|---|
STR | 1 word (2 characters) | 16-bit × 1 |
STR[N] | N words (2N characters) | 16-bit × N |
ASCII encoding + low byte first (LS spec). Trailing NUL bytes are trimmed.
// 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]
Reads one word and extracts only bit idx (same convention as Modbus).
format | Behavior |
|---|---|
BIN | Read word, then extract bit 0 |
BIN[5] | Extract bit 5 |
BIN[F] | Extract hex F (=15) bit (a single character may be hexadecimal) |
Common Errors and Solutions
| Message / Symptom | Cause | Solution |
|---|---|---|
LS-PLC Ping failed : <ip> | ICMP unreachable | Check cabling/firewall. If ICMP is blocked in your environment, review the blocking policy |
LS connect failed | FEnet module not running / port mismatch | Check the FEnet module IP/Port in XG5000 (usually 2004) |
| Value is always 0 | short form → width mismatch (read as DWord although it is a Word) | Leave format empty or specify it exactly as DW |
| 32-bit float is corrupted | format missing → read as Word | Specify data_type=Float + format=REAL |
| String characters are garbled | LS low-byte-first not applied | Edge handles this automatically. Be careful when interpreting bytes directly from outside |
| Bit is not read | short form was entered as D00100 | Use M02704 (M area) or format=BIN[idx] |
curl Registration Example
Example mixing the D and M areas of XGI / XBC / XBM:
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 values:
curl -s http://<edge-host>/api/v1/tag/OPC_LS_XBM_0001_TAG_00003/value | jq
Operating Notes
- Read branch priority (
LSDriver.read()):data_type=StringorformatisSTR/STR[N]→readStringMultiformatisBINorBIN[idx]→ read word, then extract bit- Explicit
formatbranch (UI,DW,UL,LW,REAL,LREAL, etc.) →readByFormat - Fallback:
data_typealone (readByDataType)
clientcalls are synchronized, so multiple tag reads on one OPC are serialized. Different OPCs run on separate threads.- write is not implemented. To use Sparkplug NCMD,
FEnetClient.write*must be exposed in a follow-up.
Example Collection (by data type)
All combinations frequently used in LS XGI / XBC / XBM field operations, in a single table. plc_address is given in short form and is automatically converted to %D... standard notation.
Bit / Boolean
data_type | format | plc_address | Memory width | Meaning / Notes |
|---|---|---|---|---|
Boolean | (empty) | M02704 | 1 bit | Memory area bit. The most common form |
Boolean | (empty) | P0001F | 1 bit | LS XGI hexadecimal bit index (F=15). When use-hex-bit-index=true |
Boolean | (empty) | K00010 | 1 bit | Keep relay bit |
Boolean | (empty) | T0110 | 1 bit | Timer output bit |
Boolean | BIN | D00309 | 16 bit → bit 0 | Read word, then extract bit 0 |
Boolean | BIN[3] | D00309 | 16 bit → bit 3 | Extract bit 3 |
Boolean | BIN[F] | D00309 | 16 bit → bit 15 | Hex index F=15 |
16-bit Integer (Integer/Word)
data_type | format | plc_address | Memory width | Meaning |
|---|---|---|---|---|
Integer | (empty) | D00309 | 16 bit | signed Word (%DW00309) |
Integer | UI | D00300 | 16 bit | UWord (unsigned, client.readUWord) |
Integer | UWORD | D00300 | 16 bit | Same as UI |
32-bit Integer (DWord)
data_type | format | plc_address | Memory width | Meaning |
|---|---|---|---|---|
Integer | DW | D00600 | 32 bit | DWord signed (%DD00600 and D00600/00601 combined) |
Integer | DWORD | D00600 | 32 bit | Same as above |
Integer | DUW | D00690 | 32 bit | UDWord (unsigned 32) |
Integer | UDW | D00690 | 32 bit | Same as UDW |
Integer | UL | D00690 | 32 bit | ULong = UDWord (alias) |
64-bit Integer (LWord)
data_type | format | plc_address | Memory width | Meaning |
|---|---|---|---|---|
Long | (empty) | D00700 | 64 bit | LWord signed (%DL00700) |
Long | LW | D00700 | 64 bit | Explicit LWord |
Long | LWORD | D00700 | 64 bit | Same |
Long | ULW | D00750 | 64 bit | ULWord (unsigned 64) |
Floating Point (Float / Double)
data_type | format | plc_address | Memory width | Meaning |
|---|---|---|---|---|
Float | (empty) | D00450 | 32 bit | IEEE 754 single (%DD00450) |
Float | REAL | D00450 | 32 bit | Explicit |
Float | DW | D00450 | 32 bit | Handled as float via the isFloat(dt) branch |
Double | LREAL | D00500 | 64 bit | IEEE 754 double (%DL00500) |
Double | (empty) | D00500 | 64 bit | Same as LREAL |
String
data_type | format | plc_address | Memory width | Meaning |
|---|---|---|---|---|
String | STR | D00803 | 16 bit (1 word) | 2 chars (low byte first) |
String | STR[5] | D00800 | 80 bit (5 words) | 10 chars, NUL trim |
String | STR[10] | D00500 | 160 bit (10 words) | 20 chars |
String | STR[16] | D00100 | 32 words | 32 chars (32 bytes) — recommended XGI alignment |
Using Formulas (LS field patterns)
| Purpose | data_type | fomula | Input → Output |
|---|---|---|---|
| Integer raw → 1 decimal place | Float | ${VALUE}*0.1 | 1638 → 163.8 |
| Integer raw → 2 decimal places | Float | ${VALUE}*0.01 | 12345 → 123.45 |
| signed 16 → unsigned conversion | Integer | ${VALUE}+65536 | Corrects negative raw values (16-bit conversion) |
| Zero-point correction (another tag) | Float | ${VALUE}-${TAG_ZERO} | 1024 - 24 = 1000 |
| Calibration (gain × x + offset) | Float | ${VALUE}*${TAG_GAIN}+${TAG_OFFSET} | Correction coefficients in separate tags |
| Hz conversion (rpm → Hz) | Float | ${VALUE}/60 | 1800 → 30.0 |
For detailed formula usage, see the Formula Reference.
Comprehensive curl Example (all types at once)
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"}
]
}'