# Modbus 服务器

> 工作站自己的 Modbus TCP 服务器：它在通信栏里的那一行、打开和关闭、端口和已连接的客户端、提供点位、Logic 变量和报警状态的寄存器映射、客户端读写的内容、一个完整的例子，以及它不做的事。

**Modbus 服务器**让只会说 Modbus TCP 的 PLC、HMI 或楼宇系统读取工作站的点位、Logic 变量和报警状态，并写入你允许写入的点位和变量。它是数据离开工作站的第二条路，与[内置的 OPC UA 服务器](connector-opcua-server)并列；它不是驱动：驱动去读设备，而 Modbus 服务器是被设备读的一方，它等着 PLC 连上来，自己从不连接任何东西。面板在标题下面就这么说：“为通过 Modbus TCP 读写的 PLC、HMI 和楼宇系统提供应答。它只接受连接，从不主动连接。”

Modbus 不带用户名，也不带密码：谁能到达这个端口，谁就能读取映射提供的每一个寄存器。所以服务器一开始是关闭的，映射里的每一行一开始都是只读的，写入要由选择了它的人一行一行地放开。有“查看 Connector”可以阅读这个页面；有“配置 Connector”可以修改它。

## 栏里的那一行 {#the-row-in-the-column}

**Modbus 服务器**这一行位于通信栏树里的“服务器”文件夹中、OPC UA 服务器那一行的下面，驱动显示插头的地方它显示一个服务器图标。它总是在那里：每个工作站正好有一个，它从不被创建也从不被删除。搜索框按名称找到它，状态筛选块按状态给它归类：关闭时在**已禁用**下，端口被占用或无法监听时在**有问题**下，其余时候在**正常**下。名称旁边写着服务器正在做什么，一个圆点把它概括出来：

| 这一行写着 | 圆点 | 何时 |
| --- | --- | --- |
| “已关闭” | 灰色 | 服务器处于关闭状态，每个工作站一开始都是这样。 |
| “正在端口 502 上监听”，接着是已连接的客户端数：“没有已连接的客户端”“已连接 1 个客户端”“已连接 3 个客户端” | 绿色 | 服务器已打开，运行时正在运行：端口是开着的。 |
| “端口 502 已被占用” | 红色 | 服务器已打开，但另一个程序占着这个端口。 |
| “无法在端口 502 上监听” | 红色 | 服务器已打开，但 Windows 因为别的原因拒绝打开这个端口。 |
| “运行时已停止” | 灰色 | 服务器已打开，运行时已停止：端口保持关闭，直到运行时再次运行。 |

这个圆点只属于服务器：它不计入“驱动”文件夹的汇总，也不计入状态栏，那两处说的是工作站所读的设备。选中这一行会打开服务器的面板，面板的标题区在同一个圆点旁边重复这个状态。命令在命令栏上，这一行本身没有菜单。

## 打开和关闭 {#turning-it-on-and-off}

| 命令 | 作用 | 何时置灰（情况） | 何时不画出来（角色） |
| --- | --- | --- | --- |
| **启用** | 按悬停提示所说打开服务器：“打开服务器：运行时运行期间，它在端口 502 上监听。”运行时在运行时端口立即打开，操作记录条显示“Modbus 服务器已打开。” | 服务器已经打开（“服务器已经打开。”）、有运行记录占用着工作站，或者运行时已停止；悬停提示给出原因。 | 没有“配置 Connector”时。 |
| **禁用** | 关闭服务器：“关闭服务器：它关闭端口并断开所有客户端。”操作记录条显示“Modbus 服务器已关闭。” | 服务器已经关闭（“服务器已经关闭。”）、有运行记录占用着工作站，或者运行时已停止。 | 没有“配置 Connector”时。 |

打开与否是工作站记住的一项设置，而监听跟着运行时走：

| 时刻 | 服务器做什么 |
| --- | --- |
| 工作站启动 | 留在打开状态的服务器，等运行时起来、它恢复的运行记录都回来之后再打开端口；留在关闭状态的服务器保持关闭。 |
| 端口打不开 | 这一行写着“端口 502 已被占用”或“无法在端口 502 上监听”，“端口和客户端”卡片在端口字段下面写出“端口 502 在这台计算机上已被占用。请关闭占用它的程序，或者另选一个端口。”，或者同样的失败后面跟着 Windows 给出的原因。工作站照常运行，只是没有它。服务器不会自己重试：端口空出来以后，把它关掉再打开，或者把它移到另一个端口。 |
| 运行时被停止 | 端口关闭，所有客户端被断开；还在等待答复的写入不再等待。这一行写着“运行时已停止”，重新启动运行时会再次打开端口。 |
| 应用程序关闭 | 端口随其他一切一起关闭。 |

## “端口和客户端”卡片 {#the-port-and-clients-card}

面板上的第一张卡片是**端口和客户端**。

| 字段 | 作用 | 取值与默认值 |
| --- | --- | --- |
| **TCP 端口** | 服务器监听的端口，在这台计算机的每一个地址上监听，包括 IPv4 和 IPv6。服务器监听时修改它，会关闭旧端口并打开新端口，所有客户端都会被断开；操作记录条显示“Modbus 服务器已移到端口 5020。” | 1 到 65535。默认 502，即 Modbus 的标准端口。 |
| **已连接的客户端** | 现在有多少客户端已连接：“没有已连接的客户端”“已连接 1 个客户端”“已连接 3 个客户端”。只要有人连着，一张表会逐个列出，带**客户端地址**（它的 IP 地址和端口，例如 `192.168.0.20:51234`）和**连接时间**（日期和时间，按工作站的格式）。 | 只读。 |

服务器不能迁过去的端口，会在保存任何东西之前被拒绝：原因写在字段下面，字段回到正在使用的端口，服务器留在原处。有六种输入会被拒绝：超出范围的数字（“端口必须在 1 到 65535 之间。”）；在[远程访问](remote-access)里设定的 Web 服务器端口（“端口 N 已经是 Web 服务器的端口。请另选一个端口，或者先移动 Web 服务器。”）；在[代理](agent)页面上为代理访问设定的端口，无论代理访问开着还是关着（“端口 N 已经被代理访问占用。请另选一个端口，或者先移动代理访问。”）；[配置](connector-opcua-configuration)里 OPC UA 服务器的端口，无论它在运行还是已停止（“端口 N 已经是 OPC UA 服务器的端口。请另选一个端口，或者先移动服务器。”）；Connector 页面上[MQTT Broker](connector-mqtt-broker)的端口，无论 Broker 开着还是关着（“端口 N 已经是 MQTT Broker 的端口。请另选一个端口，或者先移动 MQTT Broker。”）；以及这台计算机上此刻被别的东西占着的端口（“端口 N 在这台计算机上已被占用。请关闭占用它的程序，或者另选一个端口。”）。另外四个端口字段也会反过来拒绝这个服务器的端口。原因一直留在字段下面，直到字段接受下一个端口，或者这一行被重新选中。

卡片下面有一条说明：“响应任意单元 ID。只有 Windows 防火墙放行 TCP 端口 502 的入站连接，其他计算机才能访问这个服务器：是否放行由你决定，本工作站从不自行放行。”

服务器同时最多接受 16 个客户端：第 17 个连接一打开就会被关闭，诊断日志会记下这件事。一个没有关闭连接就消失的客户端（断电的 PLC、被拔掉的网线）会被 TCP 保活机制发现，它占的位置大约一分半钟内释放。

### Windows 防火墙 {#windows-firewall}

说明下面，一个圆点和一句话说明工作站自己的那条入站规则有没有放行这个端口，就像[客户端访问](connector-opcua-client-access#windows-firewall)卡片为 OPC UA 端口说明的那样；没有放行时给出一个动作。

| 状态 | 文字 |
| --- | --- |
| 已放行（绿色） | “端口 N 上的入站 Modbus 连接对专用网络和域网络放行。” |
| 未配置（警示色调） | “网络客户端可能被阻止，因为没有本应用拥有的规则放行端口 N。”**放行本地网络**按钮画出来。 |
| 不支持（灰色） | “这种启动方式无法管理 Windows 防火墙。如有需要，请在操作系统中配置入站访问。”开发运行、绿色运行或者演示运行，以及任何不是装出来的那个应用的运行方式。 |
| 未知（灰色） | “无法确定 Windows 防火墙的状态。” |

| 命令 | 作用 | 何时置灰（情况） | 何时不画出来（角色） |
| --- | --- | --- | --- |
| **放行本地网络** | 以提权方式启动应用自己那个助手，于是 Windows 会请求管理员批准，然后为这个端口添加规则，限定在专用和域配置文件、本地子网以及这个程序上。这期间写着“正在应用…”；操作记录条显示“正在为 Modbus 端口 N 申请 Windows 防火墙放行…”，然后是“Windows 防火墙现在允许端口 N 上的 Modbus 连接。”，或者把状态文字当作错误报出来。批准提示被取消时，状态留在“未配置”。 | 在另一台设备的浏览器里（“防火墙规则只能从工作站本机的桌面应用中应用。”），有运行记录占用着工作站或者运行时已停止时，以及规则正在应用时（“正在应用防火墙规则。”）。 | 没有“配置 Connector”时。 |

卡片打开时读一遍这个状态，端口改动后再读一遍；因此新端口一直显示“未配置”，直到你把它放行。工作站从不自行添加这条规则。

## 寄存器映射 {#the-register-map}

第二张卡片**寄存器映射**就是服务器所提供的内容：每一行把一个点位、一个 Logic 变量或一个报警状态放到客户端寻址的某个寄存器或线圈上。没有任何一行覆盖的寄存器，对客户端来说并不存在。卡片上写着：“每一行在一个寄存器上提供一个点位、一个 Logic 变量或一个报警状态。在你允许写入之前，行保持只读，提供报警的行则始终只读。允许后，客户端的写入与其他所有写入走同一条路径，并连同客户端地址记入诊断日志。”

### 表格 {#the-table}

| 列 | 显示什么 |
| --- | --- |
| **寄存器类别** | 保持寄存器（4xxxx）、输入寄存器（3xxxx）、线圈（0xxxx）或离散输入（1xxxx），每一类都带着手册地址给它的首位数字。 |
| **寄存器** | 这一行起始的寄存器（从 0 开始计数）和它占用的最后一个寄存器：一个寄存器的行写 `0`，Float 写 `0-1`。 |
| **手册地址** | 同一个位置，用客户的手册或 PLC 工具写的记法表示：类别的首位数字后接从 1 起数的寄存器。保持寄存器 0 是 `40001`，放在那里的 Float 是 `40001 到 40002`，超过 9999 用六位（保持寄存器 9999 是 `410000`）。 |
| **数据类型** | Boolean、Int16、UInt16、Int32、Float 或 String。 |
| **访问** | 只读，或读写。 |
| **信号源** | 信号源，按选择框给它的名称。 |
| **当前值** | 客户端此刻在那里读到的值；读取这一行的客户端会收到异常 04 时显示一个短横。 |

各行先按类别（保持寄存器、输入寄存器、线圈、离散输入）排列，再按寄存器排列。一行都没有时显示“还没有提供任何寄存器。用新建行添加一个。”点击一行会选中它，并在表格下面打开它的字段；选中的是哪一行，各个窗口各自记着。

让表格增长的命令和结束一行的命令位于卡片标题的右边：

| 命令 | 作用 | 何时置灰（情况） | 何时不画出来（角色） |
| --- | --- | --- | --- |
| **新建行** | 在第一个没有被任何行占用的保持寄存器上添加一行：Float，两个字节序复选框都勾选，只读，还没有信号源。这一行被选中，操作记录条显示“已在 保持寄存器（4xxxx） 40001 到 40002 添加行。”保持寄存器全部用完时显示“没有空闲的保持寄存器可用于新行。” | 有运行记录占用着工作站，或者运行时已停止。 | 没有“配置 Connector”时。 |
| **删除行**（危险色） | 立即删除表格里选中的那一行，不再询问；它的悬停提示点出这一行（“删除位于 保持寄存器（4xxxx） 40001 到 40002 的行”），操作记录条显示“已删除位于 保持寄存器（4xxxx） 40001 到 40002 的行。”它所提供的点位或变量不受影响。从此读取这些寄存器的客户端会收到异常 02。 | 没有选中行（“请在寄存器映射中选择要删除的行。”）、有运行记录占用着工作站，或者运行时已停止。 | 没有“配置 Connector”时。 |

### 一行的字段 {#a-rows-fields}

一行的字段和规则与 [Modbus 点位](driver-modbus-tcp#tag-addressing)完全相同：线的一端接受的点，另一端也接受。每个字段在改变的那一刻就保存（数字在你离开字段或按 Enter 时，选项在你选中时，复选框在你勾选时），客户端从下一个请求起就按新的行得到应答，不会被断开。

| 字段 | 作用 | 取值与默认值 |
| --- | --- | --- |
| **寄存器类别** | 这一行在 Modbus 的哪一张表里应答。选线圈或离散输入会让类型变成 Boolean；选输入寄存器或离散输入会让这一行变成只读。 | 保持寄存器（4xxxx）、输入寄存器（3xxxx）、线圈（0xxxx）、离散输入（1xxxx）。新行为保持寄存器。 |
| **寄存器编号（从 0 开始）** | 这一行从哪里开始，即请求里传输的那个编号：保持寄存器 0 就是传统记法里的 40001。旁边的**手册中的地址**用这种记法读出这一行，和**手册地址**列一样。 | 0 到 65535。新行为第一个空闲的保持寄存器。 |
| **数据类型** | 值怎样排布，与 Modbus 点位的排布方式相同（[数值类型与数量](driver-modbus-tcp#value-types-and-quantities)）：Boolean 占一个寄存器（1 或 0）或作为一位，Int16 和 UInt16 占一个寄存器，Int32 和 Float 占两个，String 按 UTF-8 每个寄存器两个字节。线圈和离散输入只能是 Boolean。在寄存器行上选 Boolean 会把它移到离散输入（只读）或线圈（读写）；在线圈或离散输入上选数字会把它移到输入寄存器或保持寄存器，与 Modbus 点位的面板一样。 | Boolean、Int16、UInt16、Int32、Float、String。新行为 Float。 |
| **长度（寄存器数）** | 只在 String 时画出：文本占用多少个寄存器。 | 留空表示 4。1 到 65535。 |
| **高字节在前（MSB first，大端序）** | 每个寄存器里哪个字节先传；括号里是手册对这一项的写法。String 不画出，线圈和离散输入也不画出。 | 勾选。 |
| **高字在前（high word first）** | 两个寄存器中哪一个承载高半部分。只在 Int32 和 Float 时画出。两个复选框都勾选时，Float 按规范的顺序 `AB CD` 传输（[字节序与字序](driver-modbus-tcp#byte-and-word-order)）；复选框下面的**数值的字节顺序**说出两个框合起来的排布（ABCD、CDAB、BADC 或 DCBA）。 | 勾选。 |
| **访问** | 客户端能否写这一行。在输入寄存器或离散输入上，**读写**仍留在列表里但置灰，悬停提示写着“只有保持寄存器和线圈接受写入。”；在提供报警的行上同样置灰，提示“提供报警的行为只读。” | **只读**或**读写**。新行为只读。 |
| **信号源** | 这一行提供什么。列表以“（无）”开头，接着是每个已启用设备上每个已启用的点位，以它的驱动、设备和它自己命名（“点位 · Simulated · Oven · Temperature”），再接着是每个持有值的 Logic 变量（布尔值、整数、小数、文本、日期和时间），以它的名称列出（“变量 · Setpoint”；放在文件夹里的变量连同文件夹一起列出，如“变量 · Oven / Setpoint”，同名的变量也能分清）。随后是所有报警，按同样方式命名（“报警 · Oven / High temperature”）；提供报警的行始终只读，提供**提供的报警状态**所指定的状态。这一行按标识保存信号源，所以给点位或变量改名，或把设备放进文件夹，这一行仍然停在同一个点上。列表不再提供的信号源仍保持选中，并照样显示名称：已禁用的点位显示为“点位 · Simulated · Oven · Temperature（已禁用）”，已禁用设备上的点位显示为“点位 · Simulated · Oven · Temperature（设备已禁用）”；只有已经不存在的信号源才显示为点位缺失或变量缺失，后面带它的标识。点位的[使用者](connector-tags#the-used-by-card)卡片会列出提供它的行，全部替换会把这些行移到所选的点位。 | 新行为“（无）”。 |
| **提供的报警状态** | 提供报警的行上显示：客户端读取报警的哪一个状态。**活动（Boolean）** 在条件成立期间为 1，**已确认（Boolean）** 在没有待确认时为 1，**状态（数字）** 是以数字表示的状态。 | 新的报警行为 活动（Boolean）。 |

字段下面有三行回读这一行：它在哪里（“占用寄存器 0。”或“占用寄存器 0 到 1。”），客户端能对它做什么（“客户端可以读取这一行，但不能写入。”或“客户端可以读写这一行：每次写入都会连同其地址记入诊断日志。”，提供报警的行则是“客户端读取这个报警状态，从不写入。作为数字，0 为正常，1 为活动未确认，2 为活动已确认，3 为已复归未确认。”），以及客户端此刻在那里读到什么（“当前值：21.5”）。读取这一行的客户端得不到值时，后面会跟上原因：

| 面板显示 | 何时 |
| --- | --- |
| “这一行还没有提供任何值：请选择信号源。” | 没有选择信号源。 |
| “这一行的信号源已不存在。读取这一行的客户端会收到异常 04。” | 点位或变量已被删除。 |
| “信号源当前没有有效值。在恢复之前，读取这一行的客户端会收到异常 04。” | 点位没有有效值（设备不应答而点位不提供替代值、点位或其设备已禁用、超限故障让质量变坏），或者变量是一个缺口。 |
| “信号源的值不符合这一行的类型。读取这一行的客户端会收到异常 04。” | 这种类型的行装不下的值：整数行里的 NaN，或比这一行的寄存器更长的文本。 |

### 一行会拒绝什么 {#what-a-row-refuses}

服务器规则拒绝的修改不会保存：原因以红字显示在这一行的字段上方，修改留在屏幕上等你改正，在有修改被接受之前，这一行继续提供原来的内容。其他行不受影响。

| 面板显示 | 何时 |
| --- | --- |
| “寄存器编号必须在 0 到 65535 之间。” | 超出协议范围的寄存器。 |
| “Modbus 点只能是 Boolean, Int16, UInt16, Int32, Float, String。” | 这六种以外的类型，只有代理才能提出。 |
| “线圈或离散输入只能是 Boolean。” | 线圈或离散输入里的数字，只有代理才能提出。 |
| “文本占用 1 到 65535 个寄存器。” | 超出范围的 String 长度。 |
| “寄存器 65535 到 65536 超出了寄存器 65535。” | 会在最后一个寄存器之后才结束的行：这里是位于 65535 的 Float。 |
| “寄存器 40001 到 40002 与位于 保持寄存器（4xxxx） 40002 的行重叠。” | 同一类别的两行共用一个寄存器。 |
| “输入寄存器和离散输入对客户端是只读的：只有保持寄存器和线圈接受写入。” | 在输入类别上放开写入，只有代理才能提出。 |
| “接受写入的行需要指定写入目标点位或变量。” | 在选信号源之前先选了读写，或者在允许写入的行上把信号源改回“（无）”。请先选信号源。 |
| “该信号源不能写入：请选择有写访问权限的点位或内部存储值变量，或者让这一行保持只读。” | 在信号源是只读点位、从不写入的驱动的点位、表达式变量或已不存在的信号源的行上放开写入。 |
| “信号源保存的是文本或日期，只有 String 类型的行能承载。” | 把文本或日期点位，或者文本、日期和时间变量放进任何其他类型的行。 |
| “提供报警的行为只读：客户端读取报警状态，从不写入。” | 在提供报警的行上要求读写，这只有代理能要求。 |
| “寄存器映射中的这一行已不存在。请重新加载后重试。” | 你编辑的时候，这一行在别处被删除了，比如另一个窗口或某个代理。 |

在有运行记录占用着工作站或运行时已停止期间，修改会像页面上的其他修改一样被拒绝，原因显示在操作记录条上（“有单元在运行时配置是锁住的。”“运行时已停止”），什么都不会保存。

## 客户端读写的内容 {#what-a-client-reads-and-writes}

服务器响应任意单元 ID，并用被询问时的那个单元 ID 来应答：在 TCP 上，工作站靠 IP 地址寻址。寄存器编号按从 0 开始计数的方式传输。从 1 开始计数、或者使用传统记法的 PLC 工具，要自己加上偏移：

| 寄存器类别 | 读取用的功能 | 写入用的功能 | 寄存器 0 的传统记法 |
| --- | --- | --- | --- |
| 保持寄存器（4xxxx） | 3 | 6（一个寄存器）或 16（多个） | 40001 |
| 输入寄存器（3xxxx） | 4 | 无：只读 | 30001 |
| 线圈（0xxxx） | 1 | 5（一个线圈）或 15（多个） | 00001 |
| 离散输入（1xxxx） | 2 | 无：只读 | 10001 |

其他任何功能码都会收到异常 01。一个请求最多读取 125 个寄存器或 2000 个线圈或输入，最多写入 123 个寄存器或 1968 个线圈。

读取要么整个应答，要么不应答。每个值都从工作站的当前状态读取，从不为客户端临时计算：点位提供它的工程值，即经过[数值转换链](connector-value-pipeline)之后工作站显示的那个值，变量提供它自己的值，报警提供其所在行指定的状态。值通过工作站唯一的一套转换变成这一行的类型：整数行里实数四舍五入到最近的整数（一半时远离零），并卡在类型的上下限，所以 Int16 行对 40000 提供 32767。读取可以只要两寄存器行的一部分：它得到所要的寄存器，按编码后的值所持有的样子。信号源没有有效值的行，绝不会得到服务器留下的最后一个值：寄存器不带质量，读到旧数字的 PLC 会照着一个工作站明知已不是当前值的数去动作。客户端改而收到异常 04，由 PLC 自己的通信故障处理来决定；代价是一行没有值会让跨过它的整块读取失败，是哪一行，映射的当前值列会指出来。自己提供替代值的点位（在它的[无效读数](connector-value-pipeline)阶段设定的、保持住的最后一个有效值或声明的数字）一直有有效值，这个替代值会像其他值一样被提供。

客户端的写入与所有其他写入经过同一个唯一的写入通道（见 [Logic](logic#writing-values)）：点位经过它的设备的投递队列，工程值按点位的转换反推回去；内部存储值变量经过 Logic 的队列。信号源锁照样生效：正在运行的 Process 的配方或联锁占着的信号源会被拒绝。写入通道把每个值以旧值到新值的形式，在来源 `Modbus client 192.168.0.20:51234` 下记入诊断日志，应答要等到每个值的回执，对点位来说回执就是设备自己的答复。

- 一次写入要覆盖整行：只写 Float 行的一个寄存器会被拒绝，所以 Float 要用功能 16、数量 2 来写。
- 写入涉及的每一行，都在任何值发出之前与映射核对：没有任何行覆盖的寄存器，或者只读的行，会让整个请求被拒绝，什么都不会写入。
- 然后这些值按寄存器顺序送往各自的信号源。写入通道对其中某一个给出的拒绝，就是客户端收到的应答，尽管同一请求里的其他行也已经交出去了。

| 异常 | 何时 |
| --- | --- |
| 01（非法功能） | 1、2、3、4、5、6、15 和 16 以外的功能码。 |
| 02（非法数据地址） | 没有任何行覆盖的寄存器；写入只读行或一行的一部分；超出 65535 的范围；写入通道说不能写入的信号源。 |
| 03（非法数据值） | 超出范围的数量或格式错误的请求；用开或关以外的值写线圈；信号源接受不了的值。 |
| 04（从站设备故障） | 没有有效值的信号源、已不存在的信号源，或这一行装不下的值；设备拒绝或没有答复的写入，或没有连接的设备。 |
| 06（从站设备忙） | 正在运行的 Process 占着的信号源，或者正在停止的运行时。 |

## 一个完整的例子 {#a-worked-example}

一台 PLC 从工作站读取一台烤炉的温度和炉门开关，并写入烤炉的设定值，即内部存储值变量 `Variables.Oven.Setpoint`。在面板上：

1. 选中 **Modbus 服务器**这一行，按**新建行**。把**寄存器类别**设为输入寄存器（3xxxx）（寄存器仍是 0），把**信号源**设为“点位 · Simulated · Oven · Temperature”。这一行显示“占用寄存器 0 到 1。”，**手册中的地址**显示 30001 到 30002，当前值列显示温度。
2. 再按一次**新建行**，把**寄存器类别**设为离散输入（1xxxx）（类型变成 Boolean，寄存器仍是 0），然后选择炉门开关“点位 · Simulated · Oven · DoorOpen”。
3. 第三次按**新建行**。它落在保持寄存器 0 上，类型 Float。先选“变量 · Oven / Setpoint”，再把**访问**设为**读写**：这一行显示“客户端可以读写这一行：每次写入都会连同其地址记入诊断日志。”
4. 按命令栏上的**启用**：这一行显示“正在端口 502 上监听”。要让另一台计算机上的 PLC 连上来，再按“端口和客户端”卡片上的**放行本地网络**。

两个字节序复选框都勾选时（Float 按大端传输，高字在前，`ABCD`）PLC 发出的请求：

| 值 | 功能 | 地址 | 数量 | 传统记法 |
| --- | --- | --- | --- | --- |
| 温度（读取） | 4 | 0 | 2 | 30001-30002 |
| 炉门开关（读取） | 2 | 0 | 1 | 10001 |
| 设定值（写入） | 16 | 0 | 2 | 40001-40002 |
| 设定值（回读） | 3 | 0 | 2 | 40001-40002 |

用代理来做同样的事，靠 Connector 范围里的四个工具：

```text
modbus_map_upsert(source: "Connector.Simulated.Oven.Temperature", registerClass: "InputRegister", register: 0, type: "Float")
modbus_map_upsert(source: "Connector.Simulated.Oven.DoorOpen", registerClass: "DiscreteInput", register: 0)
modbus_map_upsert(source: "Variables.Oven.Setpoint", registerClass: "HoldingRegister", register: 0, type: "Float", access: "ReadAndWrite")
modbus_server_configure(enabled: true)
```

代理创建的行是只读的，除非 `access` 另有说明；不给类型时，线圈或离散输入就是 Boolean。`modbus_server_status` 把整个服务器读回来：它的状态、它的客户端，以及每一行和客户端此刻在那里读到的值。这些工具在[工具，按范围列出](agent-tools#connector--configure)里有说明。

## 权限与锁定 {#permissions-and-locks}

| 权限 | 在这里开放什么 |
| --- | --- |
| 查看 Connector | 这一行、面板和寄存器映射，只读。卡片在说明“更改 Connector 需要“配置 Connector”权限。”下面不可操作，任何动作都不画出来：没有启用，没有禁用，没有新建行，没有删除行。 |
| 配置 Connector | 页面上的一切：打开和关闭服务器、它的端口，以及映射的每一行。 |

在有运行记录占用着工作站或运行时已停止期间，动作仍然画出来，置灰并附上原因，每一项修改都会被拒绝，和 [Connector](connector#engineering-boundaries) 页面的其他地方一样。阅读面板，以及客户端读取服务器，都会继续。代理只能通过[代理访问](agent-areas)的 Connector 配置范围来修改服务器，它做的每一项修改都以代理的名义记录。

## 诊断日志 {#the-journal}

服务器在事件的[控制台](events-console)里，以 Server 范围、组件 Modbus server 写入：开始和停止监听的时刻、没能打开的端口、因为已经在服务 16 个客户端而关闭的连接，以及它拒绝的每一个请求及其异常码。反复出现的拒绝，比如 PLC 每个周期都去问映射里没有的寄存器，第一次照写，持续期间每隔几分钟汇总一次，客户端重新得到应答时用一行收尾。连接和断开属于细节，留给[详细捕获](events-console#detailed-capture)。服务器设置或映射的每一次修改也会记录，无论来自页面还是来自代理。客户端的写入做了什么，是写入通道自己的那一行，来源为 `Modbus client <address>:<port>`。

## 服务器不做的事 {#what-the-server-does-not-do}

- 它只提供带 MBAP 报头的 Modbus TCP。没有串行线路上的 Modbus RTU 或 ASCII 服务器，也没有 TCP 上的 RTU 帧。
- 它从不自行打开 Windows 防火墙（只有你按下的**放行本地网络**会打开），也不能只在一块网卡上监听：它在计算机的每一个地址上、用一个端口监听。
- 它对任意单元 ID 都用同一张映射应答：不能承载多个映射不同的单元。
- 它没有用户、没有密码、也没有加密，因为 Modbus 本身没有：谁能到达端口，谁就能读每一行，并写每一行允许写入的行。除非客户端必须写入，否则让各行保持只读，并对无关的网络关闭这个端口。
- 它从不连接任何东西。要读取 Modbus 设备，请把它添加到 [Modbus TCP](driver-modbus-tcp) 或 [Modbus RTU](driver-modbus-rtu) 驱动下面。
- 它没有自己的寄存器：没有任何行覆盖的寄存器并不存在，客户端写入的一切都落到某个点位或变量里。
- 它从不保留自己的值去顶替缺失的值，也不执行其他任何功能码（Read/Write Multiple Registers、Mask Write Register、设备识别）：它们都会收到异常 01。
- 映射没有自己的导入或导出：它随着工作站的配置一起保存在[备份](settings-backups)里。
