# omron-tcp-fins **Repository Path**: note-java/omron-tcp-fins ## Basic Information - **Project Name**: omron-tcp-fins - **Description**: FINS TCP 通信:基于 Netty 的欧姆龙 PLC 客户端,支持握手、字/位读写、断线自动重连、优雅关闭。 节点自动解析:PLC 节点号在 TCP 握手成功后自动从握手响应解析,无需手动配置。 订阅监控:可配置订阅点位,按点位独立轮询间隔周期读取,值变化时回调监听器(日志输出 / 自定义监听),并维护读取统计。 专用日志:交互报文、订阅值变化通过 MDC - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2024-07-25 - **Last Updated**: 2026-10-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: finsTcp协议连接欧姆龙, fins-tcp, IOT, 欧姆龙, omorn ## README # [欧姆龙Fins读写指令大全](https://www.cnblogs.com/resplendent/p/15219216.html) ## 项目功能 - **FINS TCP 通信**:基于 Netty 的欧姆龙 PLC 客户端,支持握手、字/位读写、断线自动重连、优雅关闭。 - **节点自动解析**:PLC 节点号在 TCP 握手成功后自动从握手响应解析,无需手动配置。 - **订阅监控**:可配置订阅点位,按点位独立轮询间隔周期读取,值变化时回调监听器(日志输出 / 自定义监听),并维护读取统计。 - **专用日志**:交互报文、订阅值变化通过 MDC 标记分别输出到独立日志文件,名称可配置(带默认值)。 - **线程池配置**:订阅调度、通知分发、异步读写三组线程池均可通过 `application.yml` 调整。 - **SID 并发控制**:00-FF 共 256 个服务 ID 池化管理,支持同一连接上的并发读写与请求超时控制。 - **多设备连接**:支持同时连接多台 PLC,每台设备拥有**独立的 SID 队列、在途请求表与节点上下文**,设备之间互不影响。 ## 多设备连接 ### 配置 顶层 `omron.fins-*` 配置构成**默认设备**(不配 `omron.devices` 即单设备模式,完全向后兼容); `omron.devices` 列表可注册附加设备: ```yaml omron: fins-tcp-ip: 192.168.1.5 # 默认设备(读写/订阅不指定设备时使用) fins-tcp-port: 9600 pc-id-node: 5 devices: # 附加设备(可选) - name: plc-2 # 设备名称(唯一,缺省 = ip:port) fins-tcp-ip: 192.168.1.6 fins-tcp-port: 9600 pc-id-node: 6 - name: plc-3 fins-tcp-ip: 192.168.1.7 fins-tcp-port: 9600 pc-id-node: 7 ``` - 每台设备独立建立 TCP 连接、独立断线重连、独立握手解析节点 - **每台设备拥有完整的 00-FF 共 256 个 SID**,互不影响:一台设备的并发读写不会占用另一台设备的 SID 额度 - 设备名称缺省为 `ip:port`;建议配置 `name` 以便读写/订阅时指定 ### 代码读写(设备重载) `FinsUtil` 所有读写方法均有两种形态:不带设备参数=默认设备,第一个参数传设备名=指定设备: ```java // 默认设备(无设备参数,单设备用法不变) float f = finsUtil.readFloat("D6004", 1); finsUtil.writeWord("D6004", 1, "100"); // 指定设备(第一个参数为设备名) float f2 = finsUtil.readFloat("plc-2", "D6004", 1); boolean ok = finsUtil.writeBool("plc-2", "D6000", 1, true); // 异步同样支持:readWordAsync(device, address, offset) / writeFloatAsync(device, ...) 等 CompletableFuture f3 = finsUtil.readFloatAsync("plc-2", "D6004", 1); ``` ### 订阅点位归属设备 订阅点位增加可选 `device` 字段(为空=默认设备): ```yaml subscribe: points: - name: "1号线温度" device: plc-2 # 归属 plc-2(缺省为默认设备) address: "D6004" offset: 1 data-type: FLOAT interval-ms: 1000 enabled: true ``` - 某台设备断开时**仅暂停该设备的订阅点位**,重连成功后自动恢复;其他设备不受影响 - REST 新增点位同样支持 `device` 字段;`/subscribe/stats` 返回按设备的点位数与变化次数统计 ### REST 读写指定设备 `/send/read`、`/send/write` 增加可选参数 `device`(缺省=默认设备): ``` GET /send/read?device=plc-2&db=D6004&dataType=float GET /send/write?device=plc-2&db=D6004&dataType=float&dbValue=3.14 ``` ## 配置说明(application.yml) ```yaml omron: fins-tcp-ip: 192.168.100.202 # PLC IP fins-tcp-port: 9600 # FINS TCP 端口 pc-id-node: 61 # PC 节点(PLC 节点由握手响应自动解析,无需配置) read-write-timeout: 3 # 读写超时(秒) connect-timeout-ms: 5000 # TCP 连接超时(毫秒) reconnect-delay-ms: 3000 # 断线重连延迟(毫秒) # 专用日志(名称均可手动配置,未配置时使用默认值) log: mdc-key: finsLogCategory # 路由专用日志的 MDC 键名 interact-value: interact # 交互报文 MDC 取值 change-value: change # 值变化 MDC 取值 interact-log-name: omron-tcp-fins_interact # 交互报文日志文件名 change-log-name: omron-tcp-fins_change # 值变化日志文件名 # 订阅监控 subscribe: enabled: true default-interval-ms: 1000 # 点位未单独配置间隔时的默认值 first-read-trigger: true # 全局默认:首次读取是否触发值变化(优先级低于点位级配置,默认true) points: - name: "D6004-温度" # 点位名称(唯一) address: "D6004" # 地址:D/C/W/H/A/E/T 字或位 offset: 1 # WORD=读取个数 / BIT=位号(0~15) data-type: WORD # WORD 或 BIT interval-ms: 1000 # 轮询间隔(毫秒) first-read-trigger: true # 点位级:首次读取是否触发值变化(优先级高于全局 first-read-trigger) enabled: true description: "示例点位" # 线程池 thread-pool: subscribe: # 订阅轮询调度线程池 core-pool-size: 4 keep-alive-seconds: 60 notify: # 值变化通知分发线程池 core-pool-size: 2 max-pool-size: 4 queue-capacity: 1024 keep-alive-seconds: 60 read: # 异步读写线程池 core-pool-size: 4 max-pool-size: 8 queue-capacity: 512 keep-alive-seconds: 60 ``` ## 专用日志 | 日志文件 | 内容 | 说明 | | ---- | ---- | ---- | | `logs/omron-tcp-fins/interact/omron-tcp-fins_interact.log` | FINS 收发报文(十六进制) | 交互报文单独输出,不进入通用 info 日志 | | `logs/omron-tcp-fins/change/omron-tcp-fins_change.log` | 订阅点位值变化(点位、地址、旧值→新值) | 值变化单独输出,不进入通用 info 日志 | 实现方式:打印上述日志前向 MDC 写入标记键值(`omron.log.mdc-key`),logback 中 `FinsCategoryFilter` 据此将日志路由到独立文件;通用 `info` 日志中排除这两类日志。 如需调整文件名或 MDC 键值,修改 `omron.log.*` 即可,未配置时使用默认值。 ## 订阅监控 订阅点位支持**两种注册方式**,可并存: | 方式 | 入口 | 时机 | 说明 | | ---- | ---- | ---- | ---- | | ① 配置文件 | `application.yml` → `omron.subscribe.points` | 应用启动 | 对应设备连接后自动轮询;断线重连自动恢复 | | ② 外部传入 | `FinsClientConfig#connect(List)`(默认设备)/ `connect(deviceName, List)`(指定设备) | 创建连接时 | 连接成功后自动注册并启动;与 yml 点位共存,点位名重复时跳过,断线重连不重复注册 | ### ① 配置文件注册 在 `application.yml` 的 `omron.subscribe.points` 中配置点位,应用启动并连接 PLC 后自动开始轮询; 连接断开自动暂停全部点位,重连成功后自动恢复。 **首次读取是否触发(`first-read-trigger`)**:控制点位首轮读取到值时是否记为一次值变化。 - `true`(默认):首读 `null → 值` 记为一次变化,写入变化日志并通知监听器; - `false`:首读仅建立基线(记录当前值),不触发变化日志、变化计数与监听回调,之后值真正变化时才触发。 配置优先级:**点位级 `first-read-trigger` > 全局 `omron.subscribe.first-read-trigger` > 内置默认 `true`**。 点位未配置该字段时使用全局值(全局未配置时默认 `true`);可通过 yml 或 REST 新增点位时配置。 ### ② 连接时外部传入注册 创建连接时直接指定订阅点位,连接成功后自动注册并启动轮询: ```java // 构造外部订阅点位 FinsSubscribePoint point = new FinsSubscribePoint(); point.setName("产线A-温度"); point.setAddress("D6004"); point.setOffset(1); point.setDataType(AreaTypeEnum.FLOAT); point.setIntervalMs(1000L); point.setEnabled(true); // 创建连接并指定订阅点位(与 yml 点位共存;同名点位跳过不重复注册) finsClientConfig.connect(Arrays.asList(point)); // 也可先无参连接,之后随时用 REST / addPoint 动态注册 finsClientConfig.connect(); ``` 两种方式共享同一套点位表与统计(`/subscribe/list`、`/subscribe/stats` 均可查到); `first-read-trigger`、`interval-ms` 等字段语义与 yml 配置一致。 ### 并发能力与 SID 让位 - **256 是并发在途请求上限**(FINS 协议 SID 00-FF),**不是订阅点位数量上限**——订阅点位数量无硬性限制 - 每个点位轮询为串行(`reading` 防重叠标志),同一时刻占用 SID 的点位数 = 订阅线程池核心线程数(`omron.thread-pool.subscribe.core-pool-size`,默认 4),点位再多也只是排队轮询,不会报错 - **读写优先**:当可用 SID ≤ 1 时,订阅点位自动**跳过本轮**(debug 日志,不记失败、不告警),将 SID 让给手动/异步读写;SID 恢复后下一轮自动补读 - 需要提高订阅吞吐时调大 `omron.thread-pool.subscribe.core-pool-size`;订阅与读写并发叠加逼近 256 时,订阅自动降级,读写请求始终优先 ### 各区域类型订阅示例 订阅点位支持全部内存区(`D/C/W/H/A/E/T`),`data-type` 支持 `WORD`(字)、`BIT`(位)、`DWORD`(32位整数,占用连续2字)、`FLOAT`(IEEE754 浮点,占用连续2字)。 以下为各区域示例(`application.yml` 中默认 `enabled: false`,按需启用并修改地址): ```yaml points: - name: "D6004-温度" # D:数据存储器 DM(此处存 FLOAT,占用 D6004+D6005) address: "D6004" offset: 1 data-type: FLOAT interval-ms: 1000 enabled: true - name: "D6000-运行状态" # D 区位示例 address: "D6000" offset: 1 # BIT=位号(0~15),此处为 D6000.1 data-type: BIT interval-ms: 500 enabled: true - name: "C100-CIO区示例" # C:CIO 区 address: "C100" offset: 1 data-type: WORD interval-ms: 1000 enabled: false - name: "W100-工作继电器示例" # W:工作继电器 WR address: "W100" offset: 1 data-type: WORD interval-ms: 1000 enabled: false - name: "H100-保持继电器示例" # H:保持继电器 HR address: "H100" offset: 1 data-type: WORD interval-ms: 1000 enabled: false - name: "A100-辅助继电器示例" # A:辅助继电器 AR address: "A100" offset: 1 data-type: WORD interval-ms: 1000 enabled: false - name: "E100-扩展寄存器示例" # E:扩展数据寄存器 EM(当前Bank) address: "E100" offset: 1 data-type: WORD interval-ms: 1000 enabled: false - name: "T10-定时器示例" # T:定时器/计数器 TC(WORD=PV 值;BIT=完成标志) address: "T10" offset: 1 data-type: WORD interval-ms: 1000 enabled: false ``` > 位订阅(BIT):`data-type: BIT` + `offset` 填位号(0~15),如 `D6000 offset=1` 表示 D6000.1。 > 各区域是否可读取决于 PLC 实际配置(如 EM 区 Bank、定时器号),示例默认关闭以免干扰运行。 ### 值变化监听 实现 `FinsValueChangeListener` 接口并注册为 Spring Bean 即可接收所有点位变化通知(项目内置 `FinsSubscribeLogListener` 输出日志): ```java @Slf4j @Component public class MyListener implements FinsValueChangeListener { @Override public void onChange(FinsSubscribePoint point, String oldValue, String newValue) { // point.getName() / getCurrentValue() / getReadCount() ... } } ``` ### 管理接口 | 方法 | 路径 | 说明 | | ---- | ---- | ---- | | GET | /subscribe/list | 查询全部点位(含当前值与统计) | | POST | /subscribe | 新增点位(JSON,支持 device 字段),自动启动轮询 | | POST | /subscribe/{name}/start | 启动点位 | | POST | /subscribe/{name}/stop | 停止点位 | | DELETE | /subscribe/{name} | 删除点位 | | GET | /subscribe/stats | 聚合统计(读取/成功/变化次数、成功率、按设备统计) | 新增点位示例: ```json { "name": "W100-计数", "device": "plc-2", "address": "W100", "offset": 1, "dataType": "WORD", "intervalMs": 2000, "firstReadTrigger": false, "enabled": true, "description": "计数器(首读不触发变化)" } ``` ## 读写接口 ### 支持的内存区类型 | 前缀 | 区域 | 字代码 | 位代码 | | ---- | ---- | ---- | ---- | | D | 数据存储器 DM | 82 | 02 | | C | CIO 区 | B0 | 30 | | W | 工作继电器 WR | B1 | 31 | | H | 保持继电器 HR | B2 | 32 | | A | 辅助继电器 AR | B3 | 33 | | E | 扩展数据寄存器 EM(当前Bank) | A0 | 20 | | T | 定时器/计数器 TC(PV/完成标志) | 89 | 09 | > 说明:地址格式为「前缀+编号」,如 `D6004`、`W100`、`T10`。WORD 模式 `offset` 为读取个数(一次最多 1000 个);BIT 模式 `offset` 为**位号**(0~15),如 `D6000 offset=1` 表示 D6000.1。 ### REST 接口 读取(`GET /send/read`),`dataType` 支持 word/hex/signed/list/dword/float/bool/bits: | 参数 | 说明 | | ---- | ---- | | device | 设备名称(可选,缺省=默认设备;多设备时填 `omron.devices[x].name`) | | type | 内存区前缀 D/C/W/H/A/E/T,默认 D(db 自带前缀时可省略) | | db | 地址,如 D6004、W100、T10 | | offset | word/hex/list/signed 保留(默认1);bool/bits 为位号(0~15) | | dataType | word(默认)/hex/signed/list/dword/float/bool/bits | | count | 批量读取个数(signed/list/bits,默认1) | 写入(`GET /send/write`),`dataType` 支持 word/hex/list/dword/float/bool/bits;不传时按值自动推断(兼容旧用法): | 参数 | 说明 | | ---- | ---- | | device | 设备名称(可选,缺省=默认设备;多设备时填 `omron.devices[x].name`) | | type / db / offset | 同读取(offset 在 bool/bits 为位号) | | dataType | word/hex/list/dword/float/bool/bits(可省略,自动推断) | | dbValue | word=整数(-32768~65535);hex=4位十六进制;dword=32位整数;float=浮点;bool=true/false | | values | 批量值(逗号分隔):list 为十进制字;bits 为 true/false | | count | list 批量写入个数(默认按 values 个数) | 示例: ``` # 读取(默认设备) GET /send/read?type=D&db=D6004&dataType=word # 读字(十进制) GET /send/read?type=D&db=D6004&dataType=hex # 读字(十六进制) GET /send/read?type=D&db=D6004&count=3&dataType=list # 批量读3个字(无符号) GET /send/read?type=D&db=D6004&count=3&dataType=signed# 批量读3个字(有符号) GET /send/read?type=D&db=D6004&dataType=dword # 读32位整数(2字,低字在前) GET /send/read?type=D&db=D6004&dataType=float # 读32位浮点(2字) GET /send/read?db=D6000&offset=1&dataType=bool # 读位 D6000.1 GET /send/read?db=D6000&offset=0&count=4&dataType=bits# 批量读位 D6000.0~D6000.3 # 读取(指定设备) GET /send/read?device=plc-2&db=D6004&dataType=float # 读 plc-2 的 D6004 浮点 # 写入(默认设备) GET /send/write?type=D&db=D6004&dataType=word&dbValue=100 # 写字(支持负值补码) GET /send/write?type=D&db=D6004&dataType=hex&dbValue=ae14 # 写十六进制 GET /send/write?type=D&db=D6004&dataType=list&values=1,2,3 # 批量写3个字 GET /send/write?type=D&db=D6004&dataType=dword&dbValue=12345678 # 写32位整数 GET /send/write?type=D&db=D6004&dataType=float&dbValue=3.14 # 写浮点 GET /send/write?db=D6000&offset=1&dataType=bool&dbValue=true # 写位 D6000.1 GET /send/write?db=D6000&offset=0&dataType=bits&values=true,false,true,false # 批量写位 # 写入(指定设备) GET /send/write?device=plc-2&db=D6004&dataType=float&dbValue=3.14 ``` 代码中亦可通过 `FinsUtil` 进行同步/异步读写: ```java // 同步-单值(默认设备) String v = finsUtil.readWord("D6004", 1); // 十进制(0~65535) String hex = finsUtil.readWordHex("D6004", 1); // 原始十六进制 Boolean b = finsUtil.readBool("D6000", 1); // D6000.1 finsUtil.writeWord("D6004", 1, "100"); finsUtil.writeWord("D6004", 1, "-1"); // 有符号写入(16位补码) finsUtil.writeBool("D6000", 1, true); // 同步-指定设备(第一个参数为设备名,其余参数不变) float f2 = finsUtil.readFloat("plc-2", "D6004", 1); finsUtil.writeFloat("plc-2", "D6004", 1, 3.14f); // 同步-批量/有符号/32位/浮点/位 List words = finsUtil.readWords("D6004", 3); // 批量读3个字(无符号) List signed = finsUtil.readWordsSigned("D6004", 3); // 批量读3个字(有符号) finsUtil.writeWords("D6004", 3, Arrays.asList(1, 2, 3)); // 批量写3个字(支持负值) Long dword = finsUtil.readDword("D6004", 1); // 32位整数(低字在前) Float f = finsUtil.readFloat("D6004", 1); // 32位浮点 finsUtil.writeFloat("D6004", 1, 3.14f); List bits = finsUtil.readBits("D6000", 0, 4); // 批量读位 D6000.0~3 finsUtil.writeBits("D6000", 0, Arrays.asList(true, false, true, false)); // 异步(同名 + Async 后缀:readWordAsync/readWordHexAsync/readWordsAsync/readWordsSignedAsync/ // readDwordAsync/readFloatAsync/readBitsAsync/writeWordAsync/writeBoolAsync/ // writeWordsAsync/writeWordsHexAsync/writeDwordAsync/writeFloatAsync/writeBitsAsync) CompletableFuture f = finsUtil.readWordAsync("D6004", 1); CompletableFuture> f2 = finsUtil.readWordsAsync("D6004", 3); // 异步指定设备:readWordAsync("plc-2", "D6004", 1) / readFloatAsync("plc-2", "D6004", 1) 等 CompletableFuture f3 = finsUtil.readFloatAsync("plc-2", "D6004", 1); ``` ## 协议说明 以下指令均为16进制数,PC IP地址:192.168.250.24,PLC IP地址:192.168.250.23 ##### 注:1个十六进制数占0.5字节 # 一、Fins Tcp 1.握手指令 1.1. 发送 46494E53 0000000C 00000000 00000000 00000018 46494E53:ASCII编码:FINS; 0000000C:指后面跟的字节长度; 00000000:固定命令; 00000000:错误代码; 00000018:PC节点IP,当设置为0时,会自动获取节点IP。 1.2. 反馈 46494E53 00000010 00000001 00000000 00000018 00000017 46494E53:ASCII编码:FINS; 00000010:指后面跟的字节长度; 00000001:固定命令; 00000000:错误代码; 00000018:本机电脑节点IP; 00000017:PLC节点IP。 2. 读取指令 读D100开始的2个地址,注:一次最多读1000个地址。 2.1. 发送 46494E53 0000001A 00000002 00000000 80 00 02 001700 001800 FF 0101 82 006400 0002 46494E53:ASCII编码:FINS; 0000001A:指后面跟的字节长度; 00000002:固定命令; 00000000:错误代码; 80:ICF; 00:RSV; 02:GCT; 00:PLC网络地址; 17:PLC节点地址; 00:PLC单元地址; 00:PC网络地址; 18:PC节点地址; 00:PC单元地址; FF:SID; 0101:读指令; 82:读地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0); 006400:起始地址; 0002:读个数。 2.2. 反馈 46494E53 0000001A 00000002 00000000 C0 00 02 001800 001700 FF 0101 0000 AABB CCDD 46494E53:ASCII编码:FINS; 0000001A:指后面跟的字节长度; 00000002:固定命令; 00000000:错误代码; C0:ICF; 00:RSV; 02:GCT; 00:PC网络地址; 18:PC节点地址; 00:PC单元地址; 00:PLC网络地址; 17:PLC节点地址; 00:PLC单元地址; FF:SID; 0101:读指令; 0000:读取成功标识; AABB CCDD:读到的数据。 3.写入指令 往W10,W11写入AABB,CCDD 3.1. 发送 46494E53 0000001E 00000002 00000000 80 00 02 001700 001800 FF 0102 B1 000A00 0002 AABBCCDD 46494E53:ASCII编码:FINS; 0000001E:指后面跟的字节长度; 00000002:固定命令; 00000000:错误代码; 80:ICF; 00:RSV; 02:GCT; 00:PLC网络地址; 17:PLC节点地址; 00:PLC单元地址; 00:PC网络地址; 18:PC节点地址; 00:PC单元地址; FF:SID; 0102:写指令; B1:读地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0); 000A00:起始地址; 0002:写个数; AABBCCDD:写入数据。 3.2. 反馈 46494E53 00000016 00000002 00000000 C0 00 02 001800 001700 FF 0102 0000 46494E53:ASCII编码:FINS; 00000016:指后面跟的字节长度; 00000002:固定命令; 00000000:错误代码; C0:ICF; 00:RSV; 02:GCT; 00:PC网络地址; 18:PC节点地址; 00:PC单元地址; 00:PLC网络地址; 17:PLC节点地址; 00:PLC单元地址; FF:SID; 0102:写指令; 0000:写入成功标识。 # 二、Fins Udp Fins Udp与Fins Tcp很像,只是Fins Udp不需要发握手指令,指令从80(ICF)开始。 1.读取指令 读D100开始的2个通道 1.1. 发送 80 00 02 001700 001800 FF 0101 82 006400 0002 80:ICF; 00:RSV; 02:GCT; 00:PLC网络地址; 17:PLC节点地址; 00:PLC单元地址; 00:PC网络地址; 18:PC节点地址; 00:PC单元地址; FF:SID; 0101:读指令; 82:读地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0); 006400:起始地址; 0002:读个数。 1.2. 反馈 C0 00 02 001800 001700 FF 0101 0000 AABB CCDD C0:ICF; 00:RSV; 02:GCT; 00:PC网络地址; 18:PC节点地址; 00:PC单元地址; 00:PLC网络地址; 17:PLC节点地址; 00:PLC单元地址; FF:SID; 0101:读指令; 0000:读取成功标识; AABB CCDD:读到的数据。 2.写入指令 往W10,W11写入AABB,CCDD 2.1. 发送 80 00 02 001700 001800 FF 0102 B1 000A00 0002 AABBCCDD 80:ICF; 00:RSV; 02:GCT; 00:PLC网络地址; 17:PLC节点地址; 00:PLC单元地址; 00:PC网络地址; 18:PC节点地址; 00:PC单元地址; FF:SID; 0102:写指令; B1:读地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0); 000A00:起始地址; 0002:写个数; AABBCCDD:写入数据。 2.2. 反馈 C0 00 02 001800 001700 FF 0102 0000 C0:ICF; 00:RSV; 02:GCT; 00:PC网络地址; 18:PC节点地址; 00:PC单元地址; 00:PLC网络地址; 17:PLC节点地址; 00:PLC单元地址; FF:SID; 0102:写指令; 0000:写入成功标识。 # 三、错误代码 ![img.png](img.png) # 四、测试 ##### 注:DB 需要 x 256 在转16进制 #### 读取 ```TXT 读指令:46494E530000001A000000020000000080000200CA00003D00FF0101821774000001 46494E53 0000001A 00000002 00000000 80 00 02 00CA00 003D00 FF 0101:读指令 82:D字 177400:DB位 × 256 0001:偏移量 返回数据:46 49 4E 53 00 00 00 18 00 00 00 02 00 00 00 00 C0 00 02 00 3D 00 00 CA 00 FF 01 01 00 00 00 07 说明:读取D6004,返回数据为7 ``` #### 写入 ```TXT 写指令:46494E5300000023000000020000000080000200CA00003D00FF010202177000000101 46494E53 00000023 00000002 00000000 80 00 02 00CA00 003D00 FF 0102:写指令 02:D字 177000:DB位 × 256 0001:偏移量 01:写入值 返回值:46 49 4E 53 00 00 00 16 00 00 00 02 00 00 00 00 C0 00 02 00 3D 00 00 CA 00 FF 01 02 00 00 说明:写入D6000.1,值为true,长度不足四位需要补足 ```