๋ชจ๋“œ๋ฒ„์Šค(Modbus) ํ”„๋กœํ† ์ฝœ์€ PLC์™€ ๊ฐ™์€ ์ž๋™ํ™” ์ œ์–ด ์„ค๋น„๊ฐ€ ํฌํ•จ๋˜๋Š” ์‚ฐ์—…์—์„œ ๊ณต์‹์ ์ธ ํ‘œ์ค€์€ ์•„๋‹ˆ์ง€๋งŒ ๊ฑฐ์˜ ํ‘œ์ค€์ฒ˜๋Ÿผ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š” ํ†ต์‹  ํ”„๋กœํ† ์ฝœ์ด๋‹ค. ๋ชจ๋“œ๋ฒ„์Šค ํ”„๋กœํ† ์ฝœ์— ๋Œ€ํ•œ ์ŠคํŽ™์€ Modbus_Messaging_Implementation_Guide_V1_0b.pdf๋กœ ๊ณต๊ฐœ๋˜์–ด์žˆ๋‹ค. ๋ณธ์ธ์€ ์—๋„ˆ์ง€ ๋ถ„์•ผ ๋„๋ฉ”์ธ์—์„œ ์ผํ•˜๊ณ  ์žˆ๋Š” ๊ฐœ๋ฐœ์ž๋กœ ๊ฐ์ข… ์—๋„ˆ์ง€ ๊ด€๋ จ ์„ค๋น„์—์„œ๋„ ๋ชจ๋“œ๋ฒ„์Šค ํ”„๋กœํ† ์ฝœ์„ ์ง€์›ํ•˜๋Š” ์ œํ’ˆ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ค๋น„๋“ค์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋‹ค์–‘ํ•œ ์‹œ๊ณ„์—ด ๋ฐ์ดํ„ฐ๋“ค์„ ์ˆ˜์ง‘ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ๋ชจ๋“œ๋ฒ„์Šค ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. ์ž๋™ํ™” ์ œ์–ด์˜ ๋ชฉ์ ์ด ์•„๋‹ˆ๋ฏ€๋กœ ์‹œ๋ฆฌ์–ผ ํ†ต์‹ ์ด ์•„๋‹Œ SCADA, RTU, PLC์™€ ๊ฐ™์€ ์„ค๋น„์™€ TCP/IP ๋ฐฉ์‹์œผ๋กœ ํ†ต์‹ ํ•œ๋‹ค.

Modbus TCP/IP

๋ชจ๋“œ๋ฒ„์Šค ํ”„๋กœํ† ์ฝœ์€ ์ž๋™ํ™” ์„ค๋น„ ์‚ฐ์—…์—์„œ ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜์ ์ธ ์›น ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ฒฝํ—˜ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋‚˜ ์•Œ์•„์•ผํ•  ๋ฒ”์šฉ์ ์ธ ํ†ต์‹  ํ”„๋กœํ† ์ฝœ์€ ์•„๋‹ˆ๋‹ค. ๋ชจ๋“œ๋ฒ„์Šค TCP์—์„œ๋Š” MBAP(MODBUS Application Protocol) ํ—ค๋”์™€ Function Code ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ ํ”„๋ ˆ์ž„์„ ํ•˜๋‚˜๋กœ ์ „๋‹ฌํ•˜๊ฒŒ ๋˜๋ฉฐ ์ผ๋ฐ˜์ ์ธ ์†Œ์ผ“ ํ†ต์‹ ๊ณผ ๋™์ผํ•˜๋‹ค. ๋˜ํ•œ, ๋ชจ๋“œ๋ฒ„์Šค ํ”„๋กœํ† ์ฝœ์€ ๋งˆ์Šคํ„ฐ โ€ข ์Šฌ๋ ˆ์ด๋ธŒ ๊ตฌ์กฐ๋กœ ๋™์ž‘ํ•˜๋ฉฐ ๋งˆ์Šคํ„ฐ(ํด๋ผ์ด์–ธํŠธ)๋Š” ์Šฌ๋ ˆ์ด๋ธŒ(์„œ๋ฒ„)์—์„œ ์ •์˜ํ•œ ๋ฉ”๋ชจ๋ฆฌ ๋งต ์ •๋ณด๋ฅผ ํ† ๋Œ€๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋˜์–ด์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์›ํ•˜๋Š” ๋ช…๋ น์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

MBAP ํ—ค๋”

MBAP ํ—ค๋”๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” Unit Identifier๋Š” ๋ชจ๋“œ๋ฒ„์Šค TCP ์—์„œ๋Š” IP ์ฃผ์†Œ๋กœ ์‹๋ณ„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Unit Identifier๋Š” ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ํ•ญ๋ชฉ์ด์ง€๋งŒ 255(0xFF)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค. โ€ป LS ์‚ฐ์ „์˜ ์ƒ๋‹ดํ’ˆ์งˆ ํ–ฅ์ƒ ๊ต์œก ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด๋ด๋„ 0xFF๋กœ ๊ณ ์ •ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.

Function Code

Functional codes of Modbus registers

์ผ๋ฐ˜์ ์ธ ์ธก์ •๊ฐ’์„ ํ‘œํ˜„ํ•˜๋Š” ์•„๋‚ ๋กœ๊ทธ์™€ 0๊ณผ 1๋กœ ๊ตฌ์„ฑ๋˜๋Š” ์ƒํƒœ๊ฐ’์„ ํ‘œํ˜„ํ•˜๋Š” ๋””์ง€ํ„ธ๋กœ ๋‚˜๋ˆ„์–ด์„œ ๊ธฐ๋Šฅ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ฃผ์†Œ ๋ฒ”์œ„์— ๋”ฐ๋ผ 01(0x01, Read Coil)๊ณผ 02(0x02, Read Discrete Inputs)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ ์•„๋‚ ๋กœ๊ทธ๋Š” ์ฃผ๋กœ 03(0x03, Read Holding Registers)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด๋‹ค. โ€ป ๊ธฐ๋Šฅ ์ฝ”๋“œํ‘œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด 02(0x02, REad Discrete Inputs)์™€ 04(0x04, Read Input Registers)๋Š” ์ฝ๊ธฐ๋งŒ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋ฒ”์œ„์—์„œ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ์•„์ฑŒ ์ˆ˜ ์žˆ๋‹ค.

Example

๋„คํ‹ฐ ๊ธฐ๋ฐ˜์˜ digitalpetri/modbus ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ์œผ๋‚˜ steveohara/j2mod๋ผ๋Š” ์ž๋ฐ” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด ๋ชจ๋“œ๋ฒ„์Šค TCP ํ†ต์‹ ์„ ๊ตฌํ˜„ํ•˜๋Š”๊ฒŒ ๊ฐ„๋‹จํ•  ๊ฒƒ ๊ฐ™๋‹ค. ๋‹ค์Œ์˜ ์ฝ”๋“œ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ ์Šฌ๋ ˆ์ด๋ธŒ์— ๋ชจ๋“œ๋ฒ„์Šค ๋งต์„ ์ด๋ฏธ์ง€๋กœ ์ •์˜ํ•˜๊ณ  ๋งˆ์Šคํ„ฐ์—์„œ ์ •์˜๋œ ๋ชจ๋“œ๋ฒ„์Šค ๋งต์— ๋Œ€ํ•ด์„œ ์ฝ์–ด๋ณด๋Š” ์ƒ˜ํ”Œ์ด๋‹ค.

implementation 'com.ghgande:j2mod:3.1.1'
@Slf4j
public class ModbusTCP {
    public static final int TCP_UNIT_ID = 255; // 0xFF

    public static void main(String[] args) throws Exception {
        DefaultProcessImageFactory imageFactory = new DefaultProcessImageFactory();
        ProcessImageImplementation memoryMap = imageFactory.createProcessImageImplementation();

        memoryMap.addDigitalOut(imageFactory.createDigitalOut(true));

        ObservableRegister register = new ObservableRegister();
        register.setValue(1024);
        memoryMap.addRegister(register);

        ModbusSlave tcpSlave = ModbusSlaveFactory.createTCPSlave(Modbus.DEFAULT_PORT, 5, false);
        tcpSlave.addProcessImage(TCP_UNIT_ID, memoryMap);
        tcpSlave.open();

        ModbusTCPMaster modbusTCPMaster = new ModbusTCPMaster("127.0.0.1", Modbus.DEFAULT_PORT);
        modbusTCPMaster.connect();
        if (modbusTCPMaster.isConnected()) {
            BitVector bitVector = modbusTCPMaster.readCoils(TCP_UNIT_ID, 0, 1);
            boolean[] bools = new boolean[bitVector.size()];
            for (int i = 0; i < bitVector.size(); i++) {
                bools[i] = bitVector.getBit(i);
            }
            Register[] registers = modbusTCPMaster.readMultipleRegisters(TCP_UNIT_ID, 0, 1);

            log.info("booleans, {}", bools);
            log.info("registers, {}", registers);
            modbusTCPMaster.disconnect();
        }

        ModbusSlaveFactory.close();
    }
}

์Šฌ๋ ˆ์ด๋ธŒ ์ด๋ฏธ์ง€ ๊ตฌ์„ฑ ์‹œ 10001-20000์€ DigitalIn๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ 30001-40000์€ InputRegister๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ์ฝ๊ธฐ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์“ฐ๊ธฐ๋„ ๊ฐ€๋Šฅํ•œ DigitalOut๊ณผ Register์— ๋Œ€ํ•ด์„œ๋Š” ์Šฌ๋ ˆ์ด๋ธŒ์—์„œ ๋ณ€๊ฒฝ์— ๋Œ€ํ•ด ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ObservableDigitalOut๊ณผ ObservableRegister๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

PLC Simulator

ModRSsim2 ํ”„๋กœ๊ทธ๋žจ์„ ํ†ตํ•ด TCP/IP ๊ธฐ๋ฐ˜์˜ PLC ์Šฌ๋ ˆ์ด๋ธŒ์— ๋Œ€ํ•œ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ์œ„ ์ฝ”๋“œ์—์„œ ์Šฌ๋ ˆ์ด๋ธŒ ๋ฉ”๋ชจ๋ฆฌ ๋งต ์ •๋ณด๋ฅผ ๋งŒ๋“ค๊ธฐ๋ณด๋‹ค ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ์ •์˜ํ•˜๊ณ  ๋ชจ๋“œ๋ฒ„์Šค ๋งˆ์Šคํ„ฐ์ธ ํด๋ผ์ด์–ธํŠธ ๋กœ์ง ๊ตฌํ˜„์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

์ฐธ๊ณ