You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache PLC4X S7批量读取异常:请求与返回项数量不匹配

关于PLC4X读取snap7 Python模拟器标签数量限制的问题

我是PLC4X及编程领域新手,现使用Java 17(Spring Boot 2.5.0)编写服务读取本地搭建的snap7 Python模拟器数据,目标是实现每秒读取约1000个标签或尽可能快的读取速度。

我使用的是PLC4X 0.12.0版本,按照文档操作后,单次请求中标签数量在18个以内可正常读取,但当添加超过18个tagAddresses时,应用会挂起并抛出如下异常:

o.a.p.j.s.r.protocol.S7ProtocolLogic     : org.apache.plc4x.java.api.exceptions.PlcProtocolException: The number of requested items doesn't match the number of returned items

通过将标签地址列表拆分成批量处理可解决该问题,但我想了解是否是自身操作有误,或是该限制为设计意图,亦或是模拟器存在问题(我仅修改了部分可读取的值)。

我的代码如下:

@Component
public class S7Poller {

    private static final Logger LOG = LoggerFactory.getLogger(S7Poller.class);

    private PlcConnection connection;

    private static final LinkedHashMap<String, String> tagAddresses = new LinkedHashMap<>();

    @PostConstruct
    public void init() {
        try {
            connection = new DefaultPlcDriverManager()
               .getConnection("s7://localhost:1102?local-rack=0&local-slot=1");
        } catch (PlcConnectionException e) {
            e.printStackTrace();
            System.err.println("Error connecting to the PLC: " + e.getMessage());
        }
    }

    @Scheduled(cron = "${cron}")
    public void readPLC() {
        if (connection != null) {
            try {
                PlcReadRequest.Builder readRequest = connection.readRequestBuilder();

                readRequest.addTagAddress("boolean1", "%DB1.DBX0:0:BOOL");
                readRequest.addTagAddress("boolean2", "%DB1.DBX0:1:BOOL");
                readRequest.addTagAddress("boolean3", "%DB1.DBX0:2:BOOL");
                readRequest.addTagAddress("boolean4", "%DB1.DBX0:3:BOOL");
                readRequest.addTagAddress("boolean5", "%DB1.DBX0:4:BOOL");
                readRequest.addTagAddress("boolean6", "%DB1.DBX0:5:BOOL");
                readRequest.addTagAddress("boolean7", "%DB1.DBX0:6:BOOL");
                readRequest.addTagAddress("boolean8", "%DB1.DBX0:7:BOOL");
                readRequest.addTagAddress("sint1", "%DB1.DBB10:SINT");
                readRequest.addTagAddress("sint2", "%DB1.DBB11:SINT");
                readRequest.addTagAddress("sint3", "%DB1.DBB12:SINT");
                readRequest.addTagAddress("sint4", "%DB1.DBB13:SINT");
                readRequest.addTagAddress("usint1", "%DB1.DBB20:USINT");
                readRequest.addTagAddress("usint2", "%DB1.DBB21:USINT");
                readRequest.addTagAddress("int1", "%DB1.DBW30:INT");
                readRequest.addTagAddress("int2", "%DB1.DBW32:INT");
                readRequest.addTagAddress("int3", "%DB1.DBW34:INT");
                readRequest.addTagAddress("int4", "%DB1.DBW36:INT");
                readRequest.addTagAddress("int5", "%DB1.DBW38:INT");
                readRequest.addTagAddress("real1", "%DB1.DBD60:REAL");
                readRequest.addTagAddress("real2", "%DB1.DBD76:REAL");
                readRequest.addTagAddress("real3", "%DB1.DBD92:REAL");
                readRequest.addTagAddress("string1", "%DB1.DBX100:STRING");


                PlcReadRequest rr = readRequest.build();
                PlcReadResponse response = rr.execute().get();

                logResponses(response);

            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }

    private void logResponses(PlcReadResponse response) {
        LOG.info("boolean1: " + response.getBoolean("boolean1"));
        LOG.info("boolean2: " + response.getBoolean("boolean2"));
        LOG.info("boolean3: " + response.getBoolean("boolean3"));
        LOG.info("boolean4: " + response.getBoolean("boolean4"));
        LOG.info("boolean5: " + response.getBoolean("boolean5"));
        LOG.info("boolean6: " + response.getBoolean("boolean6"));
        LOG.info("boolean7: " + response.getBoolean("boolean7"));
        LOG.info("boolean8: " + response.getBoolean("boolean8"));
        LOG.info("sint1: " + response.getInteger("sint1"));
        LOG.info("sint2: " + response.getInteger("sint2"));
        LOG.info("sint3: " + response.getInteger("sint3"));
        LOG.info("sint4: " + response.getInteger("sint4"));
        LOG.info("usint1: " + response.getInteger("usint1"));
        LOG.info("usint2: " + response.getInteger("usint2"));
        LOG.info("int1: " + response.getInteger("int1"));
        LOG.info("int2: " + response.getInteger("int2"));
        LOG.info("int3: " + response.getInteger("int3"));
        LOG.info("int4: " + response.getInteger("int4"));
        LOG.info("int5: " + response.getInteger("int5"));
        LOG.info("real1: " + response.getFloat("real1"));
        LOG.info("real2: " + response.getFloat("real2"));
        LOG.info("real3: " + response.getFloat("real3"));
        LOG.info("string1: " + response.getString("string1"));
    }
}

可能的原因分析

  • 模拟器局限性:snap7 Python模拟器并非真实PLC设备,内部处理逻辑可能存在单次请求的标签数量上限,无法正确响应超过阈值的批量读取请求。
  • PLC4X旧版本缺陷:你使用的PLC4X 0.12.0是较旧的版本,可能存在批量请求解析的bug,导致请求项和响应项数量不匹配。
  • 标签地址离散性影响:请求的标签分散在DB块的不同偏移位置,PLC4X会将这类请求拆分为多个底层S7协议请求,模拟器可能无法正确合并这些请求的响应结果。

解决建议

  • 升级PLC4X版本:更换为PLC4X的最新稳定版本,新版本通常会修复协议解析和批量请求处理的兼容性问题。
  • 优化标签读取策略:将同一连续地址段的标签合并读取,比如把DB1.DBX0的8个BOOL合并为%DB1.DBB0:BYTE读取,再在代码中拆分每个位,减少请求项数量的同时提升读取效率。
  • 验证真实PLC:如果条件允许,用真实S7 PLC测试相同的批量读取请求,确认问题是否来源于模拟器。
  • 设置合理批量阈值:通过测试找到模拟器能稳定处理的最大单次标签数量,以此为阈值自动拆分请求列表,平衡读取速度和稳定性。

内容的提问来源于stack exchange,提问作者deleteJavascript

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 03:12:34