All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.xingshuangs.iot.protocol.melsec.service.McNetwork Maven / Gradle / Ivy

Go to download

目前它只是一个物联网通信的工具,包含 1、西门子S7通信协议,支持西门子S1500,S1200,S400,S300,S200Smart,西门子机床828D; 2、Modbus通信协议,支持ModbusTcp, ModbusRtuOverTcp, ModbusAsciiOverTcp, ModbusTcpServer; 3、三菱Melsec(MC)通信协议,支持PLC iQ-R系列, Q/L系列, QnA系列, A系列, 目前只测试了L系列和FX5U; 4、RTSP, RTCP, RTP, H264, MP4 (FMP4)协议,RTSP + H264 + FMP4 + WebSocket + MSE + WEB; 5、基础字节数组解析转换工具;

The newest version!
/*
 * MIT License
 *
 * Copyright (c) 2021-2099 Oscura (xingshuang) 
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.github.xingshuangs.iot.protocol.melsec.service;


import com.github.xingshuangs.iot.common.buff.ByteReadBuff;
import com.github.xingshuangs.iot.common.buff.ByteWriteBuff;
import com.github.xingshuangs.iot.common.constant.GeneralConst;
import com.github.xingshuangs.iot.exceptions.McCommException;
import com.github.xingshuangs.iot.net.client.TcpClientBasic;
import com.github.xingshuangs.iot.common.algorithm.LoopGroupAlg;
import com.github.xingshuangs.iot.common.algorithm.LoopGroupItem;
import com.github.xingshuangs.iot.protocol.melsec.enums.EMcCommand;
import com.github.xingshuangs.iot.protocol.melsec.enums.EMcDeviceCode;
import com.github.xingshuangs.iot.protocol.melsec.enums.EMcFrameType;
import com.github.xingshuangs.iot.protocol.melsec.enums.EMcSeries;
import com.github.xingshuangs.iot.protocol.melsec.model.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;

/**
 * plc network
 *
 * @author xingshuang
 */
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class McNetwork extends TcpClientBasic {

    /**
     * locker
     */
    private final Object objLock = new Object();

    /**
     * Communication callback, first parameter is tag, second is package content.
     * (通信回调,第一个参数是tag标签,指示该报文含义;第二个参数是具体报文内容)
     */
    private BiConsumer comCallback;

    /**
     * Persistence, true: long connection, false: short connection.
     * (是否持久化,默认是持久化,对应长连接,true:长连接,false:短连接)
     */
    private boolean persistence = true;

    /**
     * Frame type
     * (帧类型)
     */
    protected EMcFrameType frameType = EMcFrameType.FRAME_3E;

    /**
     * Access route, 4E, 3E by default.
     * (访问路径,默认4E,3E帧访问路径)
     */
    protected McAccessRoute accessRoute = McFrame4E3EAccessRoute.createDefault();

    /**
     * Monitoring timer, default 30000ms.
     * (监视定时器,默认:3000ms,设置读取及写入的处理完成之前的等待时间。设置连接站E71向访问目标发出处理请求之后到返回响应为止的等待时间。)
     */
    protected int monitoringTimer = 3000;

    /**
     * PLC series.
     * (PLC的类型系列)
     */
    protected EMcSeries series = EMcSeries.QnA;

    public McNetwork() {
        super();
    }

    public McNetwork(String host, int port) {
        super(host, port);
    }

    //region 底层数据通信部分

    @Override
    public void connect() {
        try {
            super.connect();
        } finally {
            if (!this.persistence) {
                this.close();
            }
        }
    }

    /**
     * Read data from server, core interaction.
     * (从服务器读取数据)
     *
     * @param req McMessageReq
     * @return McMessageAck
     */
    protected McMessageAck readFromServer(McMessageReq req) {
        byte[] reqBytes = req.toByteArray();
        if (this.comCallback != null) {
            this.comCallback.accept(GeneralConst.PACKAGE_REQ, reqBytes);
        }
        byte[] total;
        if (this.frameType == EMcFrameType.FRAME_4E || this.frameType == EMcFrameType.FRAME_3E) {
            total = this.readFromServer4E3E(reqBytes);
        } else {
            total = this.readFromServer1E(reqBytes);
        }
        if (this.comCallback != null) {
            this.comCallback.accept(GeneralConst.PACKAGE_ACK, total);
        }
        McMessageAck ack = McMessageAck.fromBytes(total, this.frameType);
        this.checkResult(req, ack);
        return ack;
    }

    /**
     * Read data from server of 1E.
     * (1E帧的通信交互)
     *
     * @param req req
     * @return ack
     */
    protected byte[] readFromServer1E(byte[] req) {
        int len;
        byte[] data = new byte[1024];
        synchronized (this.objLock) {
            this.write(req);
            len = this.read(data);
        }
        if (len < 0) {
            // McHeader 无效,读取长度不一致
            throw new McCommException(" McHeader is invalid, read length is inconsistent");
        }
        byte[] total = new byte[len];
        System.arraycopy(data, 0, total, 0, len);
        return total;
    }

    /**
     * Read data from server of 4E and 3E.
     * (3E帧和4E帧的通信交互)
     *
     * @param req req
     * @return ack
     */
    protected byte[] readFromServer4E3E(byte[] req) {
        int remainLength;
        int len;
        byte[] total;
        synchronized (this.objLock) {
            this.write(req);

            int headerLength = this.frameType == EMcFrameType.FRAME_4E ? 15 : 11;
            byte[] data = new byte[headerLength];
            len = this.read(data);
            if (len < headerLength) {
                // McHeader 无效,读取长度不一致
                throw new McCommException(" McHeader is invalid, read length is inconsistent");
            }
            McHeader3EAck header = (McHeader3EAck) McHeaderAck.fromBytes(data, this.frameType);
            remainLength = header.getDataLength() - 2;
            total = new byte[headerLength + remainLength];
            System.arraycopy(data, 0, total, 0, data.length);
            len = this.read(total, data.length, remainLength, true);
        }
        if (len < remainLength) {
            // McHeader后面的数据长度,长度不一致
            throw new McCommException("The length of the data behind the McHeader is inconsistent");
        }
        return total;
    }

    /**
     * Check req data and ack data.
     * (校验请求数据和响应数据)
     *
     * @param req req data
     * @param ack ack data
     */
    protected void checkResult(McMessageReq req, McMessageAck ack) {
        if (this.frameType == EMcFrameType.FRAME_4E && ack.getHeader().getSubHeader() != EMcFrameType.FRAME_4E.getAckSubHeader()) {
            // 4E帧类型,响应副帧头和请求副帧头不一致,请求副帧头+ req.getHeader().getSubHeader() + ",响应副帧头:" + ack.getHeader().getEndCode()
            throw new McCommException("4E frame type, the response sub header is inconsistent with the request sub header" +
                    ", the request sub header:" + req.getHeader().getSubHeader() + ", the response sub header:" + ack.getHeader().getEndCode());
        }
        if (this.frameType == EMcFrameType.FRAME_3E && ack.getHeader().getSubHeader() != EMcFrameType.FRAME_3E.getAckSubHeader()) {
            // "3E帧类型,响应副帧头和请求副帧头不一致,请求副帧头:" + req.getHeader().getSubHeader() + ",响应副帧头:" + ack.getHeader().getEndCode()
            throw new McCommException("3E frame type, the response sub header is inconsistent with the request sub header" +
                    ", the request sub header:" + req.getHeader().getSubHeader() + ", the response sub header:" + ack.getHeader().getEndCode());
        }
        if (this.frameType == EMcFrameType.FRAME_1E && ack.getHeader().getSubHeader() != req.getHeader().getSubHeader() + 0x80) {
            // "3E帧类型,响应副帧头和请求副帧头不一致,请求副帧头:" + req.getHeader().getSubHeader() + ",响应副帧头:" + ack.getHeader().getEndCode()
            throw new McCommException("1E frame type, the response sub header is inconsistent with the request sub header, error = 0x80" +
                    ", the request sub header:" + req.getHeader().getSubHeader() + ", the response sub header:" + ack.getHeader().getEndCode());
        }
        if (ack.getHeader().getEndCode() != 0) {
            String errorContent = this.extractError(ack.getHeader().getEndCode());
            // 响应返回异常,异常码
            String errorStr = String.format("The response returns an exception, an exception code:%d,%s", ack.getHeader().getEndCode(), errorContent);
            throw new McCommException(errorStr);
        }
    }

    /**
     * Extract error
     * (提取错误信息)
     *
     * @param errorCode error code
     * @return error string
     */
    private String extractError(int errorCode) {
        switch (errorCode) {
            case 0xC050:
                // 在"通信数据代码设置"中,设置ASCII代码通信时,接收了无法转换为二进制代码的ASCII代码的数据
                return "In Communication Data Code Settings, when you set up ASCII code communication, you received data in ASCII code that could not be converted to binary code";
            case 0xC051:
            case 0xC052:
            case 0xC053:
            case 0xC054:
                // 写入或读取点数超出了允许范围
                return "The number of write or read points exceeds the allowed range";
            case 0xC056:
                // 写入及读取请求超出了最大地址
                return "Write and read requests exceeded the maximum address";
            case 0xC058:
                // ASCII-二进制转换后的请求数据长度与字符部分的数据数不一致
                return "The length of the requested data after the ASCII-binary conversion is inconsistent with the number of data in the character part";
            case 0xC059:
                // 指令、子指令的指定中有错误
                return "There is an error in the assignment of instruction or subinstruction";
            case 0xC05B:
                // CPU模块无法对指定软元件尽心写入及读取
                return "The CPU module cannot write and read the specified software component";
            case 0xC05C:
                // 请求内容中有错误。(对字软元件进行了以位为单位的写入及读取等)
                return "There is an error in the request content.(The word software components are written and read in bit units, etc.)";
            case 0xC05D:
                // 未进行监视登录
                return "No monitored login";
            case 0xC05F:
                // 是无法对对象CPU模块执行的请求
                return "The request to the object CPU module could not be executed";
            case 0xC060:
                // 请求内容中有错误。(对位软元件的数据指定中有错误)
                return "There is an error in the request content.(There is an error in the data assignment of the alignment software component)";
            case 0xC061:
                // 请求数据长度与字符部分的数据数不一致
                return "The requested data length does not match the number of data in the character section";
            case 0xC0B5:
                // 指定了CPU模块中无法处理的数据
                return "Specifies data that cannot be processed in the CPU module";
            default:
                // 请查询三菱用户手册进行错误解析
                return "Please consult the Mitsubishi user manual for error resolution: " + errorCode;
        }
    }

    //endregion

    //region 软元件批量读取和写入

    /**
     * Read device batch by raw way, not support A series.
     * (软元件最原始的批量读取,不支持A系列)
     *
     * @param command           command
     * @param subCommand        sub command
     * @param deviceCode        device code
     * @param headDeviceNumber  head device number
     * @param devicePointsCount device point count
     * @return byte array
     */
    public byte[] readDeviceBatchRaw(EMcCommand command, int subCommand, EMcDeviceCode deviceCode,
                                     int headDeviceNumber, int devicePointsCount) {
        try {
            McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer);
            McDeviceAddress deviceAddress = new McDeviceAddress(deviceCode, headDeviceNumber, devicePointsCount);
            McReadDeviceBatchReqData data = new McReadDeviceBatchReqData();
            data.setSeries(this.series);
            data.setCommand(command);
            data.setSubcommand(subCommand);
            data.setDeviceAddress(deviceAddress);
            McMessageReq req = new McMessageReq(header, data);
            req.selfCheck();
            McMessageAck ack = this.readFromServer(req);
            McAckData ackData = (McAckData) ack.getData();
            return ackData.getData();
        } finally {
            if (!this.persistence) {
                this.close();
            }
        }
    }

    /**
     * Write device batch by raw way, not support A series.
     * (软元件最原始的批量写入,不支持A系列)
     *
     * @param command           command
     * @param subCommand        sub command
     * @param deviceCode        device code
     * @param headDeviceNumber  head device number
     * @param devicePointsCount device point count
     * @param dataBytes         data byte array
     */
    public void writeDeviceBatchRaw(EMcCommand command, int subCommand, EMcDeviceCode deviceCode,
                                    int headDeviceNumber, int devicePointsCount, byte[] dataBytes) {
        try {
            McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer);
            McDeviceContent deviceContent = new McDeviceContent(deviceCode, headDeviceNumber, devicePointsCount, dataBytes);
            McWriteDeviceBatchReqData data = new McWriteDeviceBatchReqData();
            data.setSeries(this.series);
            data.setCommand(command);
            data.setSubcommand(subCommand);
            data.setDeviceContent(deviceContent);
            McMessageReq req = new McMessageReq(header, data);
            req.selfCheck();
            this.readFromServer(req);
        } finally {
            if (!this.persistence) {
                this.close();
            }
        }
    }

    /**
     * Device access, batch read in word units
     * 软元件按字批量读取;
* 软元件点数 = 字的数量,而非字节数量,1个字 = 2个字节
* 不可以指定下述软元件。
* • 长定时器(触点: LTS、线圈: LTC)
* • 长累计定时器(触点: LSTS、线圈: LSTC)
* • 长变址寄存器(LZ)
* * @param deviceAddress device address * @return device content */ public McDeviceContent readDeviceBatchInWord(McDeviceAddress deviceAddress) { if (deviceAddress == null) { throw new NullPointerException("deviceAddress"); } if (deviceAddress.getDevicePointsCount() < 1) { throw new McCommException("1 < device point count"); } if (deviceAddress.getDeviceCode() == EMcDeviceCode.LTS || deviceAddress.getDeviceCode() == EMcDeviceCode.LTC || deviceAddress.getDeviceCode() == EMcDeviceCode.LSTS || deviceAddress.getDeviceCode() == EMcDeviceCode.LSTC || deviceAddress.getDeviceCode() == EMcDeviceCode.LZ ) { throw new McCommException("restricted access LTS、LTC、LSTS、LSTC、LZ"); } try { int actualLength = deviceAddress.getDevicePointsCount(); int maxLength = this.series.getDeviceBatchInWordPointsCount(); // int maxLength = 960; ByteWriteBuff buff = new ByteWriteBuff(deviceAddress.getDevicePointsCount() * 2); LoopGroupAlg.loopExecute(actualLength, maxLength, (off, len) -> { McDeviceAddress newAddress = new McDeviceAddress(deviceAddress.getDeviceCode(), deviceAddress.getHeadDeviceNumber() + off, len); McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer); McMessageReq req = McReqBuilder.createReadDeviceBatchInWordReq(this.series, header, newAddress); McMessageAck ack = this.readFromServer(req); buff.putBytes(((McAckData) ack.getData()).getData()); }); return McDeviceContent.createByAddress(deviceAddress, buff.getData()); } finally { if (!this.persistence) { this.close(); } } } /** * Device access, batch write in word units * 软元件按字批量写入;
* 软元件点数 = 字的数量,而非字节数量,1个字 = 2个字节
* 不可以指定下述软元件。
* • 长定时器(触点: LTS、线圈: LTC、当前值: LTN)
* • 长累计定时器(触点: LSTS、线圈: LSTC、当前值: LSTN)
* • 长变址寄存器(LZ) * * @param deviceContent device content */ public void writeDeviceBatchInWord(McDeviceContent deviceContent) { if (deviceContent == null) { throw new NullPointerException("deviceContent"); } if (deviceContent.getDevicePointsCount() < 1) { throw new McCommException("1 < device point count"); } if (deviceContent.getDeviceCode() == EMcDeviceCode.LTS || deviceContent.getDeviceCode() == EMcDeviceCode.LTC || deviceContent.getDeviceCode() == EMcDeviceCode.LTN || deviceContent.getDeviceCode() == EMcDeviceCode.LSTS || deviceContent.getDeviceCode() == EMcDeviceCode.LSTC || deviceContent.getDeviceCode() == EMcDeviceCode.LSTN || deviceContent.getDeviceCode() == EMcDeviceCode.LZ ) { throw new McCommException("restricted access LTS、LTC、LTN、LSTS、LSTC、LSTN、LZ"); } try { int actualLength = deviceContent.getDevicePointsCount(); int maxLength = this.series.getDeviceBatchInWordPointsCount(); // int maxLength = 960; ByteReadBuff buff = new ByteReadBuff(deviceContent.getData()); LoopGroupAlg.loopExecute(actualLength, maxLength, (off, len) -> { McDeviceContent newContent = new McDeviceContent(deviceContent.getDeviceCode(), deviceContent.getHeadDeviceNumber() + off, len, buff.getBytes(off * 2, len * 2)); McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer); McMessageReq req = McReqBuilder.createWriteDeviceBatchInWordReq(this.series, header, newContent); this.readFromServer(req); }); } finally { if (!this.persistence) { this.close(); } } } /** * Device access, batch read in bit units * 软元件按位批量读取; * 软元件点数 = 位的数量,而非字节数量,2个位 = 1个字节 * 不可以指定下述软元件。
* • 长定时器(触点: LTS、线圈: LTC)
* • 长累计定时器(触点: LSTS、线圈: LSTC)
* • 长变址寄存器(LZ)
* * @param deviceAddress device address * @return device content */ public McDeviceContent readDeviceBatchInBit(McDeviceAddress deviceAddress) { if (deviceAddress == null) { throw new NullPointerException("deviceAddress"); } if (deviceAddress.getDevicePointsCount() < 1) { throw new McCommException("1 < device point count"); } if (!EMcDeviceCode.checkBitType(deviceAddress.getDeviceCode())) { // 只能是位软元件 throw new McCommException("It can only be bit device code"); } if (deviceAddress.getDeviceCode() == EMcDeviceCode.LTS || deviceAddress.getDeviceCode() == EMcDeviceCode.LTC || deviceAddress.getDeviceCode() == EMcDeviceCode.LSTS || deviceAddress.getDeviceCode() == EMcDeviceCode.LSTC || deviceAddress.getDeviceCode() == EMcDeviceCode.LZ ) { throw new McCommException("restricted access LTS、LTC、LSTS、LSTC、LZ"); } try { int maxLength = this.series.getDeviceBatchInBitPointsCount(); // int maxLength = 7168; int length = deviceAddress.getDevicePointsCount() % 2 == 0 ? (deviceAddress.getDevicePointsCount() / 2) : ((deviceAddress.getDevicePointsCount() + 1) / 2); ByteWriteBuff buff = new ByteWriteBuff(length); LoopGroupAlg.loopExecute(deviceAddress.getDevicePointsCount(), maxLength, (off, len) -> { McDeviceAddress newAddress = new McDeviceAddress(deviceAddress.getDeviceCode(), deviceAddress.getHeadDeviceNumber() + off, len); McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer); McMessageReq req = McReqBuilder.createReadDeviceBatchInBitReq(this.series, header, newAddress); McMessageAck ack = this.readFromServer(req); buff.putBytes(((McAckData) ack.getData()).getData()); }); return McDeviceContent.createByAddress(deviceAddress, buff.getData()); } finally { if (!this.persistence) { this.close(); } } } /** * Device access, batch write in bit units * 软元件按位批量写入;
* 软元件点数 = 位的数量,而非字节数量,2个位 = 1个字节
* 不可以指定下述软元件。
* • 长定时器(触点: LTS、线圈: LTC、当前值: LTN)
* • 长累计定时器(触点: LSTS、线圈: LSTC、当前值: LSTN)
* • 长计数器(当前值: LCN)
* • 长变址寄存器(LZ) * * @param deviceContent device content */ public void writeDeviceBatchInBit(McDeviceContent deviceContent) { if (deviceContent == null) { throw new NullPointerException("deviceContent"); } if (deviceContent.getDevicePointsCount() < 1) { throw new McCommException("1 < device point count"); } if (!EMcDeviceCode.checkBitType(deviceContent.getDeviceCode())) { // 只能是位软元件 throw new McCommException("It can only be bit device code"); } if (deviceContent.getDeviceCode() == EMcDeviceCode.LTS || deviceContent.getDeviceCode() == EMcDeviceCode.LTC || deviceContent.getDeviceCode() == EMcDeviceCode.LTN || deviceContent.getDeviceCode() == EMcDeviceCode.LSTS || deviceContent.getDeviceCode() == EMcDeviceCode.LSTC || deviceContent.getDeviceCode() == EMcDeviceCode.LSTN || deviceContent.getDeviceCode() == EMcDeviceCode.LCN || deviceContent.getDeviceCode() == EMcDeviceCode.LZ ) { throw new McCommException("restricted access LTS、LTC、LTN、LSTS、LSTC、LSTN、LCN、LZ"); } try { int actualLength = deviceContent.getDevicePointsCount(); int maxLength = this.series.getDeviceBatchInBitPointsCount(); // int maxLength = 7168; ByteReadBuff buff = new ByteReadBuff(deviceContent.getData()); LoopGroupAlg.loopExecute(actualLength, maxLength, (off, len) -> { int length = len % 2 == 0 ? (len / 2) : ((len + 1) / 2); McDeviceContent newContent = new McDeviceContent(deviceContent.getDeviceCode(), deviceContent.getHeadDeviceNumber() + off, len, buff.getBytes(off / 2, length)); McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer); McMessageReq req = McReqBuilder.createWriteDeviceBatchInBitReq(this.series, header, newContent); this.readFromServer(req); }); } finally { if (!this.persistence) { this.close(); } } } //endregion //region 软元件随机读取和写入 /** * Device access, random read in word units. * 软元件按字随机读取;
* 地址中软元件点数可以忽略,默认1,就算写入也自动忽略
* 不可以指定下述软元件。
* • 长定时器(触点: LTS、线圈: LTC)
* • 长累计定时器(触点: LSTS、线圈: LSTC)
* • 长计数器(触点: LCS、线圈: LCC)
* * @param wordAddresses word device address * @param dwordAddresses dword device address * @return device content list. */ public List readDeviceRandomInWord(List wordAddresses, List dwordAddresses) { if (wordAddresses == null || dwordAddresses == null) { throw new NullPointerException("wordAddresses or dwordAddresses"); } if (wordAddresses.isEmpty() && dwordAddresses.isEmpty()) { throw new IllegalArgumentException("wordAddresses and dwordAddresses is empty"); } if (this.series.getFrameType() == EMcFrameType.FRAME_1E) { throw new McCommException("Frame 1E not supported in read device random in word"); } boolean wordAllMatch = this.checkDeviceRandomCode(wordAddresses); boolean dwordAllMatch = this.checkDeviceRandomCode(dwordAddresses); if (!wordAllMatch || !dwordAllMatch) { throw new McCommException("restricted access LTS、LTC、LSTS、LSTC、LCS、LCC"); } try { List result = new ArrayList<>(); int maxLength = this.series.getDeviceRandomReadInWordPointsCount(); // int maxLength = this.series == EMcSeries.Q_L ? 192 : 96; BiPredicate biPredicate = (i1, i2) -> i1.getLen() + i2.getLen() >= maxLength; LoopGroupItem wordItem = new LoopGroupItem(wordAddresses.size()); LoopGroupItem dwordItem = new LoopGroupItem(dwordAddresses.size()); LoopGroupAlg.biLoopExecute(wordItem, dwordItem, biPredicate, (i1, i2) -> { List newWords = wordAddresses.subList(i1.getOff(), i1.getOff() + i1.getLen()); List newDWords = dwordAddresses.subList(i2.getOff(), i2.getOff() + i2.getLen()); McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer); McMessageReq req = McReqBuilder.createReadDeviceRandomInWordReq(this.series, header, newWords, newDWords); McMessageAck ack = this.readFromServer(req); ByteReadBuff buff = new ByteReadBuff(((McAckData) ack.getData()).getData()); for (McDeviceAddress word : newWords) { result.add(McDeviceContent.createByAddress(word, buff.getBytes(2))); } for (McDeviceAddress dword : newDWords) { result.add(McDeviceContent.createByAddress(dword, buff.getBytes(4))); } }); return result; } finally { if (!this.persistence) { this.close(); } } } /** * Check device random code. * (批量随机读写软元件约束) * * @param addresses device address list * @return true:match,false:mismatch */ private boolean checkDeviceRandomCode(List addresses) { return addresses.stream().allMatch(x -> x.getDeviceCode() != EMcDeviceCode.LTS && x.getDeviceCode() != EMcDeviceCode.LTC && x.getDeviceCode() != EMcDeviceCode.LSTS && x.getDeviceCode() != EMcDeviceCode.LSTC && x.getDeviceCode() != EMcDeviceCode.LCS && x.getDeviceCode() != EMcDeviceCode.LCC); } /** * Device access, random write in word units. * 软元件按字随机写入; * 地址中软元件点数可以忽略,默认1,就算写入也自动忽略 * * @param wordContents word device address + content * @param dwordContents dword device address + content */ public void writeDeviceRandomInWord(List wordContents, List dwordContents) { if (wordContents == null || dwordContents == null) { throw new NullPointerException("wordContents or dwordContents"); } if (wordContents.isEmpty() && dwordContents.isEmpty()) { throw new IllegalArgumentException("wordContents and dwordContents is empty"); } boolean wordAllMatch = this.checkDeviceRandomCode(wordContents); boolean dwordAllMatch = this.checkDeviceRandomCode(dwordContents); if (!wordAllMatch || !dwordAllMatch) { throw new McCommException("restricted access LTS、LTC、LSTS、LSTC、LCS、LCC"); } try { int maxLength = this.series.getDeviceRandomWriteInWordPointsCount(); // int maxLength = this.series == EMcSeries.Q_L ? 1920 : 960; BiPredicate biPredicate = (i1, i2) -> i1.getLen() * 12 + i2.getLen() * 14 >= maxLength; LoopGroupItem wordItem = new LoopGroupItem(wordContents.size()); LoopGroupItem dwordItem = new LoopGroupItem(dwordContents.size()); LoopGroupAlg.biLoopExecute(wordItem, dwordItem, biPredicate, (i1, i2) -> { List newWord = wordContents.subList(i1.getOff(), i1.getOff() + i1.getLen()); List newDWord = dwordContents.subList(i2.getOff(), i2.getOff() + i2.getLen()); McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer); McMessageReq req = McReqBuilder.createWriteDeviceRandomInWordReq(this.series, header, newWord, newDWord); this.readFromServer(req); }); } finally { if (!this.persistence) { this.close(); } } } /** * Device access, random write in bit units. * 软元件按位随机写入;应指定位软元件。 * 软元件点数可以忽略,默认1,就算写入也自动忽略 * * @param bitAddresses bit device address list */ public void writeDeviceRandomInBit(List bitAddresses) { if (bitAddresses == null || bitAddresses.isEmpty()) { throw new IllegalArgumentException("bitAddresses is null or empty"); } // 软元件必须是位软元件 boolean allMatch = bitAddresses.stream().allMatch(x -> EMcDeviceCode.checkBitType(x.getDeviceCode())); if (!allMatch) { // 只能是位软元件 throw new McCommException("It can only be bit device code"); } try { int maxLength = this.series.getDeviceRandomWriteInBitPointsCount(); // int maxLength = this.series == EMcSeries.Q_L ? 188 : 94; LoopGroupAlg.loopExecute(bitAddresses.size(), maxLength, (off, len) -> { McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer); McMessageReq req = McReqBuilder.createWriteDeviceRandomInBitReq(this.series, header, bitAddresses.subList(off, off + len)); this.readFromServer(req); }); } finally { if (!this.persistence) { this.close(); } } } //endregion //region 软元件多个块批量读取和写入 /** * Device access, batch read multi blocks. * 软元件多块批量读取;
* 字访问软元件点数 = 字的数量,而非字节数量,1个字 = 2个字节;
* 位访问软元件点数 = 字的数量,而非字节数量,1个字 = 2个字节;
* 不可以指定下述软元件。
* • 长定时器(触点: LTS、线圈: LTC、当前值: LTN)
* • 长累计定时器(触点: LSTS、线圈: LSTC、当前值: LSTN)
* • 长计数器(触点: LCS、线圈: LCC、当前值: LCN)
* • 长变址寄存器(LZ) * * @param wordAddresses word address list * @param bitAddresses bit address list * @return device content list */ public List readDeviceBatchMultiBlocks(List wordAddresses, List bitAddresses) { this.checkDeviceBatchMultiBlocksCondition(wordAddresses, bitAddresses); if (this.series.getFrameType() == EMcFrameType.FRAME_1E) { throw new McCommException("Frame 1E not supported in reading device batch multi blocks"); } try { List result = new ArrayList<>(); int maxLength = this.series.getDeviceBlocksBlocksCount(); // int maxLength = this.series == EMcSeries.Q_L ? 120 : 60; BiPredicate biPredicate = (i1, i2) -> i1.getLen() + i2.getLen() >= maxLength; LoopGroupItem wordItem = new LoopGroupItem(wordAddresses.size()); LoopGroupItem bitItem = new LoopGroupItem(bitAddresses.size()); LoopGroupAlg.biLoopExecute(wordItem, bitItem, biPredicate, (i1, i2) -> { List newWords = wordAddresses.subList(i1.getOff(), i1.getOff() + i1.getLen()); List newBits = bitAddresses.subList(i2.getOff(), i2.getOff() + i2.getLen()); McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer); McMessageReq req = McReqBuilder.createReadDeviceBatchMultiBlocksReq(this.series, header, newWords, newBits); McMessageAck ack = this.readFromServer(req); ByteReadBuff buff = new ByteReadBuff(((McAckData) ack.getData()).getData()); for (McDeviceAddress word : newWords) { result.add(McDeviceContent.createByAddress(word, buff.getBytes(2 * word.getDevicePointsCount()))); } for (McDeviceAddress bit : newBits) { result.add(McDeviceContent.createByAddress(bit, buff.getBytes(2 * bit.getDevicePointsCount()))); } }); return result; } finally { if (!this.persistence) { this.close(); } } } /** * Check device batch multi blocks condition. * (批量块读写软元件前置校验) * * @param words words device address list * @param bits bits device address list */ private void checkDeviceBatchMultiBlocksCondition(List words, List bits) { if (words == null || bits == null) { throw new NullPointerException("wordAddresses or bitAddresses"); } if (words.isEmpty() && bits.isEmpty()) { throw new IllegalArgumentException("the number of wordAddresses and bitAddresses is empty"); } if (this.series.getFrameType() == EMcFrameType.FRAME_1E) { throw new McCommException("Frame 1E not supported in writing device batch multi blocks"); } // TODO: 待确认 if (this.frameType == EMcFrameType.FRAME_3E) { // 3E暂不支持批量块读写 throw new McCommException("3E Currently does not support batch block read and write"); } boolean b1 = words.stream().allMatch(x -> EMcDeviceCode.checkWordType(x.getDeviceCode())); if (!b1) { // 字软元件对应错误 throw new McCommException("word device code error"); } boolean b2 = bits.stream().allMatch(x -> EMcDeviceCode.checkBitType(x.getDeviceCode())); if (!b2) { // 位软元件对应错误 throw new McCommException("bit device code error"); } boolean wordAllMatch = this.checkDeviceBatchMultiBlocksCode(words); boolean bitAllMatch = this.checkDeviceBatchMultiBlocksCode(bits); if (!wordAllMatch || !bitAllMatch) { throw new McCommException("restricted access LTS、LTC、LTN、LSTS、LSTC、LSTN、LCS、LCC、LCN、LZ"); } } /** * Check device batch multi blocks code. * (批量块读写软元件约束) * * @param addresses device address * @return true:match,false:mismatch */ private boolean checkDeviceBatchMultiBlocksCode(List addresses) { return addresses.stream().allMatch(x -> x.getDeviceCode() != EMcDeviceCode.LTS && x.getDeviceCode() != EMcDeviceCode.LTC && x.getDeviceCode() != EMcDeviceCode.LTN && x.getDeviceCode() != EMcDeviceCode.LSTS && x.getDeviceCode() != EMcDeviceCode.LSTC && x.getDeviceCode() != EMcDeviceCode.LSTN && x.getDeviceCode() != EMcDeviceCode.LCS && x.getDeviceCode() != EMcDeviceCode.LCC && x.getDeviceCode() != EMcDeviceCode.LCN && x.getDeviceCode() != EMcDeviceCode.LZ); } /** * Device access, batch write multi blocks. * 软元件多块批量写入 * 字访问软元件点数 = 字的数量,而非字节数量,1个字 = 2个字节; * 位访问软元件点数 = 字的数量,而非字节数量,1个字 = 2个字节; * * @param wordContents word address + content * @param bitContents bit address + content */ public void writeDeviceBatchMultiBlocks(List wordContents, List bitContents) { this.checkDeviceBatchMultiBlocksCondition(wordContents, bitContents); try { BiPredicate biPredicate = (i1, i2) -> { List newWord = wordContents.subList(i1.getOff(), i1.getOff() + i1.getLen()); List newBit = bitContents.subList(i2.getOff(), i2.getOff() + i2.getLen()); int blockNum = newWord.size() + newBit.size(); // blockNum * (this.series == EMcSeries.Q_L ? 4 : 9) int count = blockNum * this.series.getDeviceBlocksWritePointsSize() + (newWord.stream().mapToInt(McDeviceAddress::getDevicePointsCount).sum() + newBit.stream().mapToInt(McDeviceAddress::getDevicePointsCount).sum()); return (blockNum >= this.series.getDeviceBlocksBlocksCount()) || count >= this.series.getDeviceBlocksWritePointsCount(); // return (blockNum >= (this.series == EMcSeries.Q_L ? 120 : 60)) || count >= 960; }; LoopGroupItem wordItem = new LoopGroupItem(wordContents.size()); LoopGroupItem bitItem = new LoopGroupItem(bitContents.size()); LoopGroupAlg.biLoopExecute(wordItem, bitItem, biPredicate, (i1, i2) -> { List newWords = wordContents.subList(i1.getOff(), i1.getOff() + i1.getLen()); List newBits = bitContents.subList(i2.getOff(), i2.getOff() + i2.getLen()); McHeaderReq header = McHeaderReq.createByFrameType(this.frameType, this.accessRoute, this.monitoringTimer); McMessageReq req = McReqBuilder.createWriteDeviceBatchMultiBlocksReq(this.series, header, newWords, newBits); this.readFromServer(req); }); } finally { if (!this.persistence) { this.close(); } } } //endregion //region 软元件boolean列表和字节数组之间数据转换 /** * Get boolean list by byte array. * (將字节数组转换为boolean列表) * * @param bytes byte array * @return boolean list */ public List getBooleansBy(byte[] bytes) { List res = new ArrayList<>(); for (byte aByte : bytes) { res.add((aByte & (byte) 0xF0) == 0x10); res.add((aByte & 0x0F) == 0x01); } return res; } /** * Get byte array by boolean list. * (将boolean列表转换为字节数组) * * @param booleans boolean list * @return byte array */ public byte[] getBytesBy(List booleans) { int len = booleans.size() % 2 == 0 ? (booleans.size() / 2) : ((booleans.size() + 1) / 2); byte[] result = new byte[len]; for (int i = 0; i < len; i++) { if (Boolean.TRUE.equals(booleans.get(i * 2))) { result[i] |= 0x10; } if (i * 2 + 1 < booleans.size() && Boolean.TRUE.equals(booleans.get(i * 2 + 1))) { result[i] |= 0x01; } } return result; } //endregion }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy