Node.js Modbus개발实战完整指南:modbus-serial库 TCP/RTU클라이언트 데이터분석与MQTT上云게이트웨이

무료무료 기술 자료

이 글은 바로 읽을 수 있으며 기초 학습과 검색 유입에 적합합니다.

Node.js以其异步非阻塞、事件驱动的特性,非常适合개발工业데이터采集和Modbus통신게이트웨이。modbus-serial是Node.js生态中最流行、最活跃的Modbus库,纯JavaScript实现,支持Modbus RTU(시리얼 포트)、TCP和RTU-over-TCP,同时提供클라이언트和服务端API。本文从安装설정、TCP/RTU클라이언트、데이터유형분석、服务端模拟、批量采集、MQTT上云到산업용게이트웨이 실전,全面讲解Node.js Modbus개발。

一、modbus-serial库개요

1.1 什么是modbus-serial

modbus-serial是一个纯JavaScript实现的Modbus프로토콜库,由Yaacov Zamir维护,在npm上每周下载量超过10万次,是Node.js生态中最成熟的Modbus솔루션。它同时支持Modbus RTU(通过시리얼 포트)、Modbus TCP和RTU-over-TCP三种传输방식,提供Promise和回调两种API风格,并内置了Modbus服务端(슬레이브)模拟功能。

1.2 核心特性

  • 多프로토콜支持:Modbus RTU(시리얼 포트)、Modbus TCP、RTU-over-TCP
  • 클라이언트+服务端:既可以做마스터采集데이터,也可以做슬레이브模拟设备
  • Promise API:所有操作返回Promise,支持async/await
  • 데이터유형분석:内置32位整数、浮点数、문자串等多레지스터데이터분석
  • 시리얼 포트支持:基于node-serialport,支持Windows/Linux/macOS
  • 연결管理:自动重连、超时控制、事务ID管理
  • 纯JavaScript:无需编译原生模块(TCP모드),跨平台
  • 活跃维护:持续更新,issue响应快,社区活跃

1.3 支持的기능 코드

기능 코드方法설명
01readCoils(addr, count)读코일
02readDiscreteInputs(addr, count)读이산 입력
03readHolding회원가입s(addr, count)读유지 레지스터
04readInput회원가입s(addr, count)读입력 레지스터
05writeCoil(addr, value)写单个코일
06write회원가입(addr, value)写单个레지스터
15writeCoils(addr, values)写多个코일
16write회원가입s(addr, values)写多个레지스터
43readDeviceIdentification()读设备标识(MEI)

二、安装与快速开始

# 安装modbus-serial
npm install modbus-serial

# 如果需要RTU시리얼 포트支持,还需安装serialport
npm install serialport

# 验证安装
node -e "const ModbusRTU = require('modbus-serial'); console.log('modbus-serial已安装');"
// quick_시작.js - 5分钟快速上手
const ModbusRTU = require("modbus-serial");

// 创建클라이언트实例
const client = new ModbusRTU();

// 设置슬레이브 주소(单元ID)
client.setID(1);

// 设置超时(毫秒)
client.setTimeout(5000);

// 연결Modbus TCP设备
async function main() {
    try {
        // 연결到Modbus TCP슬레이브
        await client.connectTCP("192.168.1.100", { port: 502 });
        console.log("已연결到Modbus TCP设备");

        // 읽기10个유지 레지스터(从주소0开始)
        const result = await client.readHolding회원가입s(0, 10);
        console.log("유지 레지스터:", result.데이터);

        // 단일 레지스터 쓰기 (FC06)(주소100,值1234)
        await client.write회원가입(100, 1234);
        console.log("已쓰기레지스터100=1234");

        // 읽기8个코일
        const coils = await client.readCoils(0, 8);
        console.log("코일 상태:", coils.데이터);

        // 닫기연결
        client.close();
        console.log("연결已닫기");
    } catch (err) {
        console.error("操作失败:", err.message);
    }
}

main();

三、Modbus TCP클라이언트详解

// tcp_client.js - Modbus TCP클라이언트完整예제
const ModbusRTU = require("modbus-serial");

class ModbusTcpClient {
    constructor(host, port = 502, unitId = 1, timeout = 5000) {
        this.client = new ModbusRTU();
        this.host = host;
        this.port = port;
        this.unitId = unitId;
        this.timeout = timeout;
        this.connected = false;
    }

    // 연결
    async connect() {
        this.client.setID(this.unitId);
        this.client.setTimeout(this.timeout);

        await this.client.connectTCP(this.host, {
            port: this.port,
            // 可选:自动重连
            // autoReconnect: true,
            // reconnectInterval: 1000,
        });
        this.connected = true;
        console.log(`已연결 ${this.host}:${this.port}, 슬레이브ID=${this.unitId}`);
    }

    // 读유지 레지스터
    async readHolding(주소, count) {
        const result = await this.client.readHolding회원가입s(주소, count);
        return result.데이터;  // 返回number数组
    }

    // 读입력 레지스터
    async readInput(주소, count) {
        const result = await this.client.readInput회원가입s(주소, count);
        return result.데이터;
    }

    // 读코일
    async readCoils(주소, count) {
        const result = await this.client.readCoils(주소, count);
        return result.데이터;  // 返回boolean数组
    }

    // 读이산 입력
    async readDiscrete(주소, count) {
        const result = await this.client.readDiscreteInputs(주소, count);
        return result.데이터;
    }

    // 写单个레지스터
    async write회원가입(주소, value) {
        await this.client.write회원가입(주소, value);
    }

    // 写多个레지스터
    async write회원가입s(주소, values) {
        await this.client.write회원가입s(주소, values);
    }

    // 写单个코일
    async writeCoil(주소, value) {
        await this.client.writeCoil(주소, value);  // value: true/false
    }

    // 닫기
    close() {
        this.client.close();
        this.connected = false;
    }
}

// 使用예제
async function main() {
    const mb = new ModbusTcpClient("192.168.1.100", 502, 1);
    await mb.connect();

    // 읽기센서데이터
    const temp = await mb.readHolding(0, 4);
    console.log("온도:", temp[0] / 10, "°C");
    console.log("湿度:", temp[1] / 10, "%");
    console.log("电压:", temp[2] / 10, "V");
    console.log("电流:", temp[3] / 100, "A");

    // 控制设备
    await mb.writeCoil(0, true);   // 启动모터
    await mb.write회원가입(10, 50); // 设置频率50Hz

    mb.close();
}

main().catch(console.error);

四、Modbus RTU시리얼 포트클라이언트

// rtu_client.js - Modbus RTU시리얼 포트클라이언트
const ModbusRTU = require("modbus-serial");

async function main() {
    const client = new ModbusRTU();
    client.setID(1);        // 슬레이브 주소
    client.setTimeout(5000); // 超时5秒

    // 시리얼 포트 연결(Linux: /dev/ttyUSB0, Windows: COM3, macOS: /dev/tty.usbserial-xxx)
    await client.connectRTU("/dev/ttyUSB0", {
        baudRate: 9600,      // 통신 속도
        데이터Bits: 8,         // 데이터 비트
        parity: "none",      // 패리티: none/even/odd
        stopBits: 1,         // 정지 비트
        // 可选:RS485自动收发控制
        // rtsControl: "hardware",
    });
    console.log("RTU시리얼 포트已연결");

    // 유지 레지스터 읽기 (FC03)
    const result = await client.readHolding회원가입s(0, 10);
    console.log("레지스터데이터:", result.데이터);

    // 읽기多个슬레이브(总线上多个设备)
    for (let id = 1; id <= 5; id++) {
        client.setID(id);
        try {
            const 데이터 = await client.readHolding회원가입s(0, 4);
            console.log(`슬레이브${id}:`, 데이터.데이터);
        } catch (err) {
            console.log(`슬레이브${id}无响应:`, err.message);
        }
    }

    client.close();
}

main().catch(console.error);

4.1 시리얼 포트设备查找

// list_ports.js - 查找可用시리얼 포트
const SerialPort = require("serialport");

async function listSerialPorts() {
    const ports = await SerialPort.list();
    console.log("可用시리얼 포트:");
    ports.forEach(port => {
        console.log(`  路径: ${port.path}`);
        console.log(`  制造商: ${port.manufacturer || "未知"}`);
        console.log(`  시퀀스号: ${port.serialNumber || "无"}`);
        console.log(`  pnpId: ${port.pnpId || "无"}`);
        console.log("---");
    });
}

listSerialPorts();

// 常见시리얼 포트路径:
// Linux:   /dev/ttyUSB0 (USB转RS485), /dev/ttyS0 (板载시리얼 포트)
// Windows: COM3, COM4
// macOS:   /dev/tty.usbserial-A123456, /dev/cu.usbserial-A123456

五、데이터유형분석

Modbus레지스터是16位的,但实际设备经常用多个레지스터组合存储32位整数、浮点数、문자串等데이터。modbus-serial提供了buffer도구类来분석这些데이터。

// 데이터_types.js - 多레지스터데이터유형분석
const ModbusRTU = require("modbus-serial");

// 方法一:使用modbus-serial内置的buffer도구
async function read데이터Types(client) {
    // 읽기8个레지스터(足够存放各种데이터유형)
    const result = await client.readHolding회원가입s(0, 8);
    const regs = result.데이터;  // [r0, r1, r2, r3, r4, r5, r6, r7]

    // ====== 16位整数(单个레지스터) ======
    const uint16 = regs[0];           // 无符号16位
    const int16 = regs[0] > 32767 ? regs[0] - 65536 : regs[0];  // 有符号16位

    // ====== 32位整数(两个레지스터) ======
    // 大端모드(高字在前):value = (r0 << 16) | r1
    const uint32_be = (regs[0] << 16) | regs[1];
    // 小端모드(低字在前):value = (r1 << 16) | r0
    const uint32_le = (regs[1] << 16) | regs[0];

    // ====== 32位浮点数(两个레지스터,IEEE 754) ======
    // 使用Buffer转换
    const buf = Buffer.alloc(4);
    buf.writeUInt16BE(regs[2], 0);  // 高字
    buf.writeUInt16BE(regs[3], 2);  // 低字
    const float32 = buf.readFloatBE(0);  // 大端浮点数

    // ====== 64位浮点数(四个레지스터) ======
    const buf64 = Buffer.alloc(8);
    for (let i = 0; i < 4; i++) {
        buf64.writeUInt16BE(regs[4 + i], i * 2);
    }
    const double64 = buf64.readDoubleBE(0);

    // ====== 문자串(多个레지스터,每个레지스터存2个ASCII문자) ======
    let str = "";
    for (let i = 0; i < 4; i++) {
        str += String.fromCharCode((regs[i] >> 8) & 0xFF);
        str += String.fromCharCode(regs[i] & 0xFF);
    }
    str = str.replace(/\0/g, "").trim();  // 去掉空문자

    console.log("16位无符号:", uint16);
    console.log("16位有符号:", int16);
    console.log("32位无符号(大端):", uint32_be);
    console.log("32位浮点数:", float32);
    console.log("64位浮点数:", double64);
    console.log("문자串:", str);
}

// 方法二:使用modbus-serial的readHolding회원가입s返回的buffer
// result.buffer 是一个Buffer对象,可以直接用표준方法읽기
async function readWithBuffer(client) {
    const result = await client.readHolding회원가입s(0, 8);
    const buf = result.buffer;  // Buffer对象

    const uint16 = buf.readUInt16BE(0);
    const uint32 = buf.readUInt32BE(2);
    const float = buf.readFloatBE(6);

    console.log("使用Buffer읽기:", uint16, uint32, float);
}

六、Modbus服务端(슬레이브)模拟

modbus-serial不仅可以做클라이언트,还可以创建Modbus TCP服务端(슬레이브),用于模拟设备或搭建데이터게이트웨이。

// modbus_server.js - Modbus TCP服务端(슬레이브)模拟
const ModbusRTU = require("modbus-serial");

// 创建服务端
const server = new ModbusRTU.Server();

// ====== 定义데이터向量(回调函数) ======
// 当마스터읽기时,这些回调函数被调用,返回对应데이터
const vector = {
    // 读유지 레지스터(기능 코드03)
    getHolding회원가입: function(addr, unitID, callback) {
        // addr: 레지스터주소
        // unitID: 슬레이브 주소
        // callback(err, value): 返回레지스터 값
        const holdingRegs = [250, 650, 2200, 135, 0, 0, 0, 0];
        if (addr >= 0 && addr < holdingRegs.length) {
            callback(null, holdingRegs[addr]);
        } else {
            callback({
                exceptionCode: 2,  // 非法주소
            });
        }
    },

    // 读입력 레지스터(기능 코드04)
    getInput회원가입: function(addr, unitID, callback) {
        const inputRegs = [100, 200, 300, 400];
        if (addr >= 0 && addr < inputRegs.length) {
            callback(null, inputRegs[addr]);
        } else {
            callback({ exceptionCode: 2 });
        }
    },

    // 读코일(기능 코드01)
    getCoil: function(addr, unitID, callback) {
        const coils = [true, false, true, true, false, false, false, false];
        if (addr >= 0 && addr < coils.length) {
            callback(null, coils[addr]);
        } else {
            callback({ exceptionCode: 2 });
        }
    },

    // 读이산 입력(기능 코드02)
    getDiscreteInput: function(addr, unitID, callback) {
        callback(null, addr % 2 === 0);
    },

    // 写单个레지스터(기능 코드06)
    set회원가입: function(addr, value, unitID, callback) {
        console.log(`写레지스터: 주소=${addr}, 值=${value}, 슬레이브=${unitID}`);
        // 在这里执行实际动作,如控制设备
        callback(null);  // 쓰기成功
    },

    // 写多个레지스터(기능 코드16)
    set회원가입Array: function(addr, values, unitID, callback) {
        console.log(`批量写레지스터: 주소=${addr}, 值=${values}`);
        callback(null);
    },

    // 写单个코일(기능 코드05)
    setCoil: function(addr, value, unitID, callback) {
        console.log(`写코일: 주소=${addr}, 值=${value}`);
        callback(null);
    },
};

// 启动TCP服务端,监听502포트
server.listenTCP(502, vector, {
    host: "0.0.0.0",
    // 可选:允许的슬레이브ID
    // unitID: 1,
});

console.log("Modbus TCP服务端已启动,监听포트502");
console.log("유지 레지스터0-3: 온도25.0°C, 湿度65.0%, 电压220.0V, 电流1.35A");
console.log("按 Ctrl+C 정지");

// 模拟센서데이터实时更新
setInterval(() => {
    // 可以在这里更新内部데이터
    // 例如从真实센서읽기데이터
}, 1000);

七、批量데이터采集与轮询

// poller.js - 산업용批量데이터 수집기
const ModbusRTU = require("modbus-serial");

class ModbusPoller {
    constructor(config) {
        this.client = new ModbusRTU();
        this.config = config;
        this.데이터 = {};        // 采集到的데이터
        this.running = false;
        this.timer = null;
    }

    // 연결
    async connect() {
        this.client.setID(this.config.unitId || 1);
        this.client.setTimeout(this.config.timeout || 5000);

        if (this.config.type === "tcp") {
            await this.client.connectTCP(this.config.host, {
                port: this.config.port || 502,
            });
        } else if (this.config.type === "rtu") {
            await this.client.connectRTU(this.config.port, {
                baudRate: this.config.baudRate || 9600,
                parity: this.config.parity || "none",
            });
        }
        console.log("已연결到Modbus设备");
    }

    // 采集单个레지스터组
    async readGroup(group) {
        try {
            const result = await this.client.readHolding회원가입s(
                group.주소, group.count
            );
            // 按설정분석데이터
            group.points.forEach((point, idx) => {
                let value = result.데이터[idx];
                // 적용缩放스케일 계수
                if (point.scale) value = value * point.scale;
                // 적용偏移
                if (point.offset) value = value + point.offset;
                // 保留小数位
                if (point.decimals !== undefined) {
                    value = Number(value.toFixed(point.decimals));
                }
                this.데이터[point.name] = value;
            });
            return true;
        } catch (err) {
            console.error(`采集 ${group.name} 失败:`, err.message);
            return false;
        }
    }

    // 启动周期性采集
    시작(intervalMs = 1000) {
        this.running = true;
        const poll = async () => {
            if (!this.running) return;
            for (const group of this.config.groups) {
                await this.readGroup(group);
            }
            // 출력当前데이터
            console.log(JSON.stringify(this.데이터, null, 2));
            this.timer = setTimeout(poll, intervalMs);
        };
        poll();
    }

    // 정지
    stop() {
        this.running = false;
        if (this.timer) clearTimeout(this.timer);
        this.client.close();
    }
}

// 설정예제:采集一个智能电表
const config = {
    type: "tcp",
    host: "192.168.1.100",
    port: 502,
    unitId: 1,
    groups: [
        {
            name: "电매개변수",
            주소: 0,
            count: 6,
            points: [
                { name: "电压", scale: 0.1, decimals: 1, unit: "V" },
                { name: "电流", scale: 0.01, decimals: 2, unit: "A" },
                { name: "有功功率", scale: 0.001, decimals: 3, unit: "kW" },
                { name: "无功功率", scale: 0.001, decimals: 3, unit: "kvar" },
                { name: "功率因数", scale: 0.001, decimals: 3 },
                { name: "频率", scale: 0.01, decimals: 2, unit: "Hz" },
            ],
        },
        {
            name: "电能",
            주소: 100,
            count: 2,
            points: [
                { name: "正向有功电能", scale: 0.01, decimals: 2, unit: "kWh" },
                { name: "反向有功电能", scale: 0.01, decimals: 2, unit: "kWh" },
            ],
        },
    ],
};

// 运行
async function main() {
    const poller = new ModbusPoller(config);
    await poller.connect();
    poller.시작(2000);  // 每2秒采集一次
}

main().catch(console.error);

八、Modbus转MQTT上云实战

// modbus_mqtt_gateway.js - Modbus转MQTT物联网게이트웨이
const ModbusRTU = require("modbus-serial");
const mqtt = require("mqtt");

// ====== 설정 ======
const MODBUS_CONFIG = {
    host: "192.168.1.100",
    port: 502,
    unitId: 1,
};

const MQTT_CONFIG = {
    url: "mqtt://broker.emqx.io",
    port: 1883,
    topic: "modbus/gateway/device001",
    clientId: "modbus_gateway_" + Math.random().toString(16).substr(2, 8),
};

const POLL_INTERVAL = 5000;  // 5秒采集一次

// ====== 데이터点설정 ======
const 데이터_POINTS = [
    { name: "temperature", 주소: 0, scale: 0.1, unit: "°C" },
    { name: "humidity", 주소: 1, scale: 0.1, unit: "%" },
    { name: "voltage", 주소: 2, scale: 0.1, unit: "V" },
    { name: "current", 주소: 3, scale: 0.01, unit: "A" },
    { name: "power", 주소: 4, scale: 0.001, unit: "kW" },
    { name: "energy", 주소: 100, scale: 0.01, unit: "kWh" },
];

// ====== 主程序 ======
async function main() {
    // 1. 연결Modbus
    const mbClient = new ModbusRTU();
    mbClient.setID(MODBUS_CONFIG.unitId);
    mbClient.setTimeout(3000);
    await mbClient.connectTCP(MODBUS_CONFIG.host, { port: MODBUS_CONFIG.port });
    console.log("Modbus已연결");

    // 2. 연결MQTT
    const mqttClient = mqtt.connect(MQTT_CONFIG.url, {
        clientId: MQTT_CONFIG.clientId,
        port: MQTT_CONFIG.port,
    });

    mqttClient.on("connect", () => {
        console.log("MQTT已연결");
        // 订阅下行控制主题
        mqttClient.subscribe(MQTT_CONFIG.topic + "/command");
    });

    // 3. 处理下行控制指令(MQTT → Modbus写)
    mqttClient.on("message", async (topic, message) => {
        try {
            const cmd = JSON.parse(message.toString());
            console.log("收到控制指令:", cmd);
            if (cmd.type === "write_register") {
                await mbClient.write회원가입(cmd.주소, cmd.value);
                console.log(`已쓰기레지스터${cmd.주소}=${cmd.value}`);
            } else if (cmd.type === "write_coil") {
                await mbClient.writeCoil(cmd.주소, cmd.value);
                console.log(`已쓰기코일${cmd.주소}=${cmd.value}`);
            }
        } catch (err) {
            console.error("控制指令执行失败:", err.message);
        }
    });

    // 4. 周期性采集并上报(Modbus → MQTT)
    async function pollAndPublish() {
        try {
            const payload = {
                deviceId: "device001",
                timestamp: new Date().toISOString(),
                데이터: {},
            };

            for (const point of 데이터_POINTS) {
                const result = await mbClient.readHolding회원가입s(point.주소, 1);
                let value = result.데이터[0];
                if (point.scale) value = value * point.scale;
                payload.데이터[point.name] = {
                    value: Number(value.toFixed(3)),
                    unit: point.unit,
                };
            }

            // 发布到MQTT
            mqttClient.publish(
                MQTT_CONFIG.topic + "/데이터",
                JSON.stringify(payload),
                { qos: 1 }
            );
            console.log("已上报:", JSON.stringify(payload.데이터));
        } catch (err) {
            console.error("采集上报失败:", err.message);
        }
    }

    // 启动采集
    setInterval(pollAndPublish, POLL_INTERVAL);
    pollAndPublish();  // 바로执行一次
}

main().catch(console.error);

九、错误处理与重连机制

十、常见문제与排查

문제原因解决方法
연결超时IP/포트错误或네트워크不通ping测试IP,telnet测试502포트,检查防火墙
返回异常码01기능 코드不支持确认设备支持该기능 코드,查阅设备手册
返回异常码02레지스터주소不存在检查주소范围,注意设备주소是0-based还是1-based
返回异常码03읽을 수량超出范围减少읽을 수량,单次最多125个레지스터
데이터值不对바이트序/缩放스케일 계수错误确认大端/小端모드,核对scale和offset
시리얼 포트打不开포트被占用或权限不足Linux需sudo或加入dialout组,检查是否被其他程序占用
RTU无响应통신 속도/패리티不일치核对设备통신매개변수,检查A/B线是否接反
频繁断连네트워크不稳定或设备연결数限制추가重连机制,使用长연결,检查设备最大연결数
并发请求冲突同时보내기多个请求使用队列串行보내기,대기响应后再发下一个
浮点数분석错误바이트序不일치尝试readFloatBE/readFloatLE,确认设备문서

十一、性能优化最佳实践

  • 批量읽기:尽量一次읽기连续주소的多个레지스터,减少请求次数
  • 长연결复用:不要频繁创建/닫기연결,保持TCP长연결
  • 串行请求:Modbus是半双工프로토콜,必须대기响应后再发下一个请求
  • 合理轮询间隔:根据데이터变化频率设置采集周期,不要过于频繁
  • 주소优化:将需要频繁읽기的레지스터安排在连续주소,便于批量읽기
  • 超时设置:TCP建议3-5秒,RTU建议根据통신 속도계산(9600통신 속도约2秒)
  • 错误重试:单次失败不要바로报错,重试1-2次后再上报异常
  • 연결池:多设备시나리오使用연결池,避免重复握手开销
  • 데이터缓存:对变化慢的데이터(如电能累计值)降低읽기频率,缓存结果
  • 内存管理:长时间运行的采集程序注意内存泄漏,定期重启或监控

十二、其他Node.js Modbus库对比

库名프로토콜클라이언트/服务端特点推荐시나리오
modbus-serialRTU/TCP/RTU-over-TCP都支持最流行,API简洁,문서完善通用시나리오,首选
modbus-rtuRTU/TCP클라이언트支持데이터유형自动转换需要复杂데이터분석
modbus-streamRTU/TCP都支持基于Stream,灵活需要底层控制
jsmodbusTCP都支持纯TCP,TypeScript支持仅TCP시나리오
node-modbusTCP클라이언트轻量简单TCP采集

十三、학습자료

  • npm包주소:https://www.npmjs.com/package/modbus-serial
  • GitHub源码:https://github.com/yaacov/node-modbus-serial
  • API문서:https://yaacov.github.io/node-modbus-serial/
  • Modbus프로토콜사양:https://modbus.org/specs.php
  • node-serialport문서:https://serialport.io/
  • MQTT.js문서:https://github.com/mqttjs/MQTT.js

Node.js凭借异步非阻塞的天然优势和modbus-serial库的成熟API,成为개발工业데이터采集게이트웨이、Modbus转MQTT桥接器和设备模拟服务的理想选择。本文覆盖了从基础연결、데이터분석到批量采集、MQTT上云、错误重连的完整개발流程,代码均可直接运行。关键是注意Modbus半双工特性(请求必须串行)、레지스터주소偏移(0-based vs 1-based)和多바이트데이터的바이트序일치这三个常见坑点。

📦

VIP专属:Node.js Modbus개발完整代码包

modbus-serial库TCP/RTU클라이언트、服务端模拟、데이터분석、MQTT上云게이트웨이、健壮重连机制。

开通VIP即可下载完整代码,同时解锁 30+ 엔지니어링实战자료包:디버깅스크립트、速查表、项目模板、排查案例……

前往VIP자료库下载 → 月费仅9.9元 / 年费199元
실제 프로젝트에 이 자료를 활용하시겠습니까?

도구 센터에서 프레임 분석, CRC 검증, 장비 디버깅을 진행하거나 요청을 제출하여 선정 및 연동 제안을 받아보세요.

엔지니어 회원

이 글을 실행 가능한 디버깅 자료로 활용하세요

가입하면 고급 프레임 분석, 자료 패키지 다운로드, 코드 예제, 엔지니어링 사례, 우선 기술 지원을 이용할 수 있어 실제 프로젝트 납품에 적합합니다.

고급 도구 무제한 이용
자료 패키지와 코드 패키지
완전한 엔지니어링 사례 라이브러리
우선 기술 지원 채널