public enum BlockType {

    /** Organization Block */
    OB(0x38),

    /** User Data Block */
    DB(0x41),

    /** System Data Block */
    SDB(0x42),

    /** User Function */
    FC(0x43),

    /** System Function */
    SFC(0x44),

    /** User Function Block */
    FB(0x45),

    /** System Function Block */
    SFB(0x46);

    private final byte value;

    BlockType(int value) {
        this.value = (byte) value;
    }

    public byte getValue() {
        return value;
    }
} public enum PlcCpuStatus {

    UNKNOWN(0x00), STOP(0x04), RUN(0x08);

    private final byte value;

    PlcCpuStatus(int value) {
        this.value = (byte) value;
    }

    public byte getValue() {
        return value;
    }

    public static PlcCpuStatus valueOf(byte b) {
        // Since RUN status is always 0x08 for all CPUs and CPs, STOP status
        // sometime can be coded as 0x03 (especially for old cpu...)
        for (PlcCpuStatus s : values()) {
            if (s.value == b) {
                return s;
            }
        }
        return RUN;
    }
} public class S7BlockInfo {

    public BlockSubType blkType;
    public BlockLang blkLang;
    public String author, family, header;
    public int blkFlags, blkNumber, checksum, loadSize, localData, mc7Size, sbbLength, version;
    public LocalDate codeDate, intfDate;

    public static S7BlockInfo of(byte[] src, int pos) {
        S7BlockInfo info = new S7BlockInfo();
        info.decode(src, pos);
        return info;
    }

    protected void decode(byte[] buffer, int pos) {
        blkFlags = S7.getUnsignedIntAt(buffer, pos);
        blkLang = BlockLang.of(S7.getByteAt(buffer, pos + 1));
        blkType = BlockSubType.of(S7.getByteAt(buffer, pos + 2));
        blkNumber = S7.getWordAt(buffer, pos + 3);
        loadSize = S7.getDIntAt(buffer, pos + 5);
        codeDate = LocalDate.of(1984, 1, 1).plusDays(S7.getWordAt(buffer, pos + 17));
        intfDate = LocalDate.of(1984, 1, 1).plusDays(S7.getWordAt(buffer, pos + 23));
        sbbLength = S7.getWordAt(buffer, pos + 25);
        localData = S7.getWordAt(buffer, pos + 29);
        mc7Size = S7.getWordAt(buffer, pos + 31);
        author = S7.getStringAt(buffer, pos + 33, 8).trim();
        family = S7.getStringAt(buffer, pos + 41, 8).trim();
        header = S7.getStringAt(buffer, pos + 49, 8).trim();
        version = S7.getUnsignedIntAt(buffer, pos + 57);
        checksum = S7.getWordAt(buffer, pos + 59);
    }

    public String getBlockFlagString() {
        return String.format("%08d", Integer.valueOf(Integer.toString(blkFlags, 2)));
    }

    public String getVersionString() {
        byte low = (byte) ((version >> 4) & 0xFF);
        byte high = (byte) ((version >> 8) & 0xFF);
        return String.format("%d.%d", low, high);
    }

    @Override
    public String toString() {
        return "S7BlockInfo [author=" + author + ", family=" + family + ", header=" + header + ", blkFlags=" + blkFlags + ", blkLang=" + blkLang
                + ", blkNumber=" + blkNumber + ", blkType=" + blkType + ", checksum=" + checksum + ", loadSize=" + loadSize + ", localData=" + localData
                + ", mc7Size=" + mc7Size + ", sbbLength=" + sbbLength + ", version=" + version + ", codeDate=" + codeDate + ", intfDate=" + intfDate + "]";
    }

} public class S7BlockList {

    private final Map<BlockSubType, Integer> map = new EnumMap<>(BlockSubType.class);

    public static S7BlockList of(byte[] src, int pos) {
        S7BlockList list = new S7BlockList();
        list.decode(src, pos);
        return list;
    }

    protected void decode(byte[] src, int pos) {
        for (int i = 0; i < 28; i += 4) {
            add(S7.getStringAt(src, pos + i, 2), S7.getWordAt(src, pos + i + 2));
        }
    }

    private void add(String blk, int count) {
        BlockSubType type = BlockSubType.of((byte) Integer.parseInt(blk, 16));
        map.put(type, count);
    }

    public int getSize(BlockSubType type) {
        return map.getOrDefault(type, 0);
    }

    @Override
    public String toString() {
        return "S7BlockList [map=" + map + "]";
    }

} public class S7CpInfo {

    public int maxBusRate;
    public int maxConnections;
    public int maxMpiRate;
    public int maxPduLength;

    public static S7CpInfo of(byte[] src, int pos) {
        S7CpInfo info = new S7CpInfo();
        info.decode(src, pos);
        return info;
    }

    protected void decode(byte[] src, int pos) {
        maxPduLength = S7.getShortAt(src, 2);
        maxConnections = S7.getShortAt(src, 4);
        maxMpiRate = S7.getDIntAt(src, 6);
        maxBusRate = S7.getDIntAt(src, 10);
    }

    @Override
    public String toString() {
        return "S7CpInfo [maxBusRate=" + maxBusRate + ", maxConnections=" + maxConnections + ", maxMpiRate=" + maxMpiRate + ", maxPduLength=" + maxPduLength
                + "]";
    }
} public class S7CpuInfo {

    public String asName;
    public String copyright;
    public String moduleName;
    public String moduleTypeName;
    public String serialNumber;

    public static S7CpuInfo of(byte[] src, int pos) {
        S7CpuInfo info = new S7CpuInfo();
        info.decode(src, pos);
        return info;
    }

    protected void decode(byte[] src, int pos) {
        asName = S7.getStringAt(src, pos + 2, 24);
        copyright = S7.getStringAt(src, pos + 104, 26);
        moduleName = S7.getStringAt(src, pos + 36, 24);
        moduleTypeName = S7.getStringAt(src, pos + 172, 32);
        serialNumber = S7.getStringAt(src, pos + 138, 24);
    }

    @Override
    public String toString() {
        return "S7CpuInfo [asName=" + asName + ", copyright=" + copyright + ", moduleName=" + moduleName + ", moduleTypeName=" + moduleTypeName
                + ", serialNumber=" + serialNumber + "]";
    }
} public class S7DataItem extends DataItem{

    public int result;
    public int amount;
    public byte[] data;

    public S7DataItem(AreaType area, DataType type, int db, int start, int amount) {
        this(area, type, db, start, amount, null);
    }

    public S7DataItem(AreaType area, DataType type, int db, int start, int amount, byte[] data) {
        super(area, type, db, start);
        int wordSize = DataType.getByteLength(Objects.requireNonNull(type));
        switch (Objects.requireNonNull(area)) {
            case CT_INPUTS:
            case CT_OUTPUTS:
                this.type = DataType.COUNTER;
                break;
            case TM:
                this.type = DataType.TIMER;
                break;
            default:
                this.type = type;
                break;
        }

        switch (this.type) {
            case BIT:
                // Only 1 bit can be transferred at time
                this.amount = 1;
                this.start = start;
            case COUNTER:
            case TIMER:
                this.amount = amount;
                this.start = start;
                break;
            default:
                // convert to Bit request
                this.amount = amount * wordSize;
                this.start = start * 8;
                break;
        }

        this.area = area;
        this.db = db;
        this.result = ReturnCode.ERR_ITEM_NOT_AVAILABLE;
        this.data = data != null ? data : new byte[this.amount];
    }

    @Override
    public String toString() {
        return "S7DataItem [" + (area != null ? "area=" + area + ", " : "") + (type != null ? "type=" + type + ", " : "") + "result=" + result + ", db=" + db
                + ", start=" + start + ", amount=" + amount + ", " + (data != null ? "data.length=" + data.length : "") + "]";
    }

} public class S7OrderCode {

    public String code;
    public int v1;
    public int v2;
    public int v3;

    public static S7OrderCode of(byte[] src, int pos, int size) {
        S7OrderCode oc = new S7OrderCode();
        oc.decode(src, pos, size);
        return oc;
    }

    public String getCode() {
        return code;
    }

    public String getFirmware() {
        return String.format("%s.%s.%s", v1, v2, v3);
    }

    protected void decode(byte[] src, int pos, int size) {
        code = S7.getStringAt(src, pos + 2, 20);
        v1 = src[pos + size - 3] & 0xFF;
        v2 = src[pos + size - 2] & 0xFF;
        v3 = src[pos + size - 1] & 0xFF;
    }

    @Override
    public String toString() {
        return "S7OrderCode [code=" + code + ", v1=" + v1 + ", v2=" + v2 + ", v3=" + v3 + "]";
    }
} public class S7Protection {

    /**
     * Startup switch setting (1:CRST, 2:WRST, 0:undefined, does not exist of cannot be determined)
     */
    public int anl_sch;

    /**
     * Mode selector setting (1:RUN, 2:RUN-P, 3:STOP, 4:MRES, 0:undefined or cannot be determined)
     */
    public int bart_sch;

    /**
     * Protection level set in parameters (0, 1, 2, 3; 0: no password, protection level invalid)
     */
    public int sch_par;

    /** Valid protection level of the CPU */
    public int sch_rel;

    /** Protection level set with the mode selector (1, 2, 3) */
    public int sch_schal;

    public static S7Protection of(byte[] src) {
        S7Protection pro = new S7Protection();
        pro.decode(src);
        return pro;
    }

    protected void decode(byte[] src) {
        sch_schal = S7.getWordAt(src, 2);
        sch_par = S7.getWordAt(src, 4);
        sch_rel = S7.getWordAt(src, 6);
        bart_sch = S7.getWordAt(src, 8);
        anl_sch = S7.getWordAt(src, 10);
    }

    @Override
    public String toString() {
        return "S7Protection [anl_sch=" + anl_sch + ", bart_sch=" + bart_sch + ", sch_par=" + sch_par + ", sch_rel=" + sch_rel + ", sch_schal=" + sch_schal
                + "]";
    }
} public class S7Szl {

    public byte[] data;
    public int dataSize;
    public int lenthdr;
    public int n_dr;

    public S7Szl(int bufferSize) {
        data = new byte[bufferSize];
    }

    public void copy(byte[] src, int srcPos, int destPos, int size) {
        System.arraycopy(src, srcPos, data, destPos, size);
    }
} public class S7Exception extends Exception {

    private static final long serialVersionUID = -3135373121524472268L;

    private final int errorCode;

    public S7Exception(int error, String message) {
        super(message);
        errorCode = error;
    }

    public S7Exception(int error, String message, Throwable th) {
        super(message, th);
        errorCode = error;
    }

    public int getErrorCode() {
        return errorCode;
    }
} public enum AreaType {
    /** System Information (caller) */
    SYS_INFO(0x03),

    /** S7 counters */
    SYSTEM_FLAGS(0x05),

    /** Analog Inputs - System info (200 family) */
    ANALOG_INPUTS(0x06),

    /** Analog Outputs - System flags (200 family) */
    ANALOG_OUTPUTS(0x07),

    /** Analog Counter Input area (200 family) */
    CT_INPUTS(0x1C),

    /** S7 Timer area */
    TM(0x1D),

    /** Analog Counter Output area (200 family) */
    CT_OUTPUTS(0x1E),

    /** IEC counters (200 family) */
    TM_IEC(0x1F),

    /** PE (inputs) instance area */
    PE(0x81),

    /** PA (outputs) instance area */
    PA(0x82),

    /** Marker (flags) area */
    MK(0x83),

    /** DataBlock (Peripheral I/O) area */
    DB(0x84),

    /** DataBlock area */
    DI(0x85),

    /** DataBlock (local) area */
    DB_LOCAL(0x86),

    /** IEC timers (200 family) */
    TM_V(0x87);

    private final byte value;

    AreaType(int value) {
        this.value = (byte) value;
    }

    public byte getValue() {
        return value;
    }

    public final static List<AreaType> SUPPORTED = Arrays.asList(MK, DB, TM);

} public enum DataType {

    /** Bit, Boolean */
    BIT(0x01),

    /** Unsigned Byte */
    BYTE(0x02),

    /** Unsigned Byte,Char */
    CHAR(0x03),

    /** Unsigned Word, Short, BCD */
    WORD(0x04),

    /** Signed Word, Short, BCD */
    INT(0x05),

    /** Unsigned Double Word, Long, LBCD, Float */
    DWORD(0x06),

    /** Signed Double Word, Long, LBCD, Float */
    DINT(0x07),

    /** Float */
    REAL(0x08),

    /** Counter type */
    COUNTER(0x1C),

    /** Timer type */
    TIMER(0x1D);

    private final byte value;

    DataType(int value) {
        this.value = (byte) value;
    }

    public byte getValue() {
        return value;
    }

    /**
     * Type to byte length
     *
     * @param type DataType
     * @return encoded byte[] length
     */
    public static int getByteLength(DataType type) {
        switch (type) {
            case BIT:// S7 sends 1 byte per bit
            case BYTE:
            case CHAR:
                return 1;
            case WORD:
            case INT:
            case COUNTER:
            case TIMER:
                return 2;
            case DWORD:
            case DINT:
            case REAL:
                return 4;
            default:
                return 0;
        }
    }
} public class S7 {

    private static final byte[] BIT_MASK = { (byte) 0x01, (byte) 0x02, (byte) 0x04, (byte) 0x08, (byte) 0x10, (byte) 0x20, (byte) 0x40, (byte) 0x80 };

    private final static char EMP_CHAR = '\u0020', DOT_CHAR = '.';
    private final static char HEX_DIGIT[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    public static int bcdToByte(byte b) {
        return ((b >> 4) * 10) + (b & 0x0F);
    }

    public static byte byteToBCD(int value) {
        return (byte) (((value / 10) << 4) | (value % 10));
    }


    public static boolean getBitAt(byte b, int bitpos) {
        int value = b & 0x0FF;
        return (value & BIT_MASK[bitpos]) != 0;
    }

    public static boolean getBitAt(byte[] buffer, int pos, int bitpos) {
        int value = buffer[pos] & 0x0FF;
        return (value & BIT_MASK[bitpos]) != 0;
    }

    public static byte[] getBytesAt(byte[] buffer, int pos, int maxLen) {
        return Arrays.copyOfRange(buffer, pos, pos + maxLen);
    }

    public static Date getDateAt(byte[] buffer, int pos) {
        int year, month, day, hour, min, sec;
        Calendar cal = Calendar.getInstance();

        year = bcdToByte(buffer[pos]);
        if (year < 90) {
            year += 2000;
        } else {
            year += 1900;
        }

        month = bcdToByte(buffer[pos + 1]) - 1;
        day = bcdToByte(buffer[pos + 2]);
        hour = bcdToByte(buffer[pos + 3]);
        min = bcdToByte(buffer[pos + 4]);
        sec = bcdToByte(buffer[pos + 5]);

        cal.set(year, month, day, hour, min, sec);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    public static LocalDateTime getDateTimeAt(byte[] buffer, int pos) {
        int year, month, day, hour, min, sec;
        year = bcdToByte(buffer[pos]);
        year += year < 90 ? 2000 : 1900;
        month = bcdToByte(buffer[pos + 1]);
        day = bcdToByte(buffer[pos + 2]);
        hour = bcdToByte(buffer[pos + 3]);
        min = bcdToByte(buffer[pos + 4]);
        sec = bcdToByte(buffer[pos + 5]);

        // First two digits of miliseconds
        // int msecH = bcdToByte(buffer[pos + 6]) * 10;
        // Last digit of miliseconds
        // int msecL = bcdToByte(buffer[pos + 7]) / 10;

        return LocalDateTime.of(year, month, day, hour, min, sec);
    }

    // Returns a 32 bit signed value : from 0 to 4294967295 (2^32-1)
    public static int getDIntAt(byte[] buffer, int pos) {
        return ((buffer[pos]) << 24 | (buffer[pos + 1] & 0xFF) << 16 | (buffer[pos + 2] & 0xFF) << 8 | (buffer[pos + 3] & 0xFF));
    }

    // Returns a 32 bit unsigned value : from 0 to 4294967295 (2^32-1)
    public static long getDWordAt(byte[] buffer, int pos) {
        return ((buffer[pos]) << 24 | (buffer[pos + 1] & 0xFF) << 16 | (buffer[pos + 2] & 0xFF) << 8 | (buffer[pos + 3] & 0xFF));
    }

    // Returns a 32 bit floating point
    public static float getFloatAt(byte[] buffer, int pos) {
        int IntFloat = getDIntAt(buffer, pos);
        return Float.intBitsToFloat(IntFloat);
    }

    public static String getPrintableStringAt(byte[] buffer, int pos, int maxLen) {
        StringBuilder sb = new StringBuilder();
        for (int i = pos; i < pos + maxLen; i++) {
            char ch = (char) (buffer[i] & 0xFF);
            if (Character.isISOControl(ch) || Character.isWhitespace(ch)) {
                sb.append(DOT_CHAR);
                continue;
            }
            sb.append(ch);
        }
        return sb.toString();
    }

    // Returns a 16 bit signed value : from -32768 to 32767
    public static int getShortAt(byte[] buffer, int pos) {
        int hi = (buffer[pos]);
        int lo = (buffer[pos + 1] & 0x00FF);
        return ((hi << 8) + lo);
    }

    public static String getStringAt(byte[] buffer, int pos, int maxLen) {
        return getStringAt(buffer, pos, maxLen, StandardCharsets.UTF_8);
    }

    public static String getStringAt(byte[] buffer, int pos, int maxLen, Charset charset) {
        return new String(buffer, pos, maxLen, charset);
    }

    public static String getS7StringAt(byte[] buffer, int pos) {
        int length = buffer[pos + 1];
        return new String(buffer, pos + 2, length, StandardCharsets.UTF_8);
    }

    public static byte getByteAt(byte[] buffer, int pos) {
        return buffer[pos];
    }

    public static int getUnsignedIntAt(byte[] buffer, int pos) {
        return Byte.toUnsignedInt(buffer[pos]);
    }

    /**
     * Returns a 16 bit unsigned value : from 0 to 65535 (2^16-1)
     *
     * @param buffer
     * @param pos start position
     * @return
     */
    public static int getWordAt(byte[] buffer, int pos) {
        int hi = (buffer[pos] & 0x00FF);
        int lo = (buffer[pos + 1] & 0x00FF);
        return (hi << 8) + lo;
    }

    public static S7Timer getS7TimerAt(byte[] buffer, int pos) {
        return S7Timer.of(buffer, pos);
    }

    public static void setBitAt(byte[] buffer, int pos, int bitPos, boolean value) {
        if (bitPos < 0) {
            bitPos = 0;
        } else if (bitPos > 7) {
            bitPos = 7;
        }
        if (value) {
            buffer[pos] = (byte) (buffer[pos] | BIT_MASK[bitPos]);
        } else {
            buffer[pos] = (byte) (buffer[pos] & ~BIT_MASK[bitPos]);
        }
    }

    public static void setDateAt(byte[] buffer, int pos, Date date) {
        int year, month, day, hour, min, sec;
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);

        year = cal.get(Calendar.YEAR);
        month = cal.get(Calendar.MONTH) + 1;
        day = cal.get(Calendar.DAY_OF_MONTH);
        hour = cal.get(Calendar.HOUR_OF_DAY);
        min = cal.get(Calendar.MINUTE);
        sec = cal.get(Calendar.SECOND);
        // milli = cal.get(Calendar.MILLISECOND);
        // // First two digits of miliseconds
        // int msecH = milli / 10;
        // // Last digit of miliseconds
        // int msecL = milli % 10;

        if (year > 1999) {
            year -= 2000;
        }
        buffer[pos] = byteToBCD(year);
        buffer[pos + 1] = byteToBCD(month);
        buffer[pos + 2] = byteToBCD(day);
        buffer[pos + 3] = byteToBCD(hour);
        buffer[pos + 4] = byteToBCD(min);
        buffer[pos + 5] = byteToBCD(sec);
        buffer[pos + 6] = byteToBCD(0);
        buffer[pos + 7] = byteToBCD(0);

    }

    public static void setDateTimeAt(byte[] buffer, int pos, LocalDateTime dateTime) {
        int year, month, day, hour, min, sec;
        year = dateTime.get(ChronoField.YEAR);
        month = dateTime.get(ChronoField.MONTH_OF_YEAR);
        day = dateTime.get(ChronoField.DAY_OF_MONTH);
        hour = dateTime.get(ChronoField.HOUR_OF_DAY);
        min = dateTime.get(ChronoField.MINUTE_OF_HOUR);
        sec = dateTime.get(ChronoField.SECOND_OF_MINUTE);
        // milli = dateTime.get(ChronoField.MILLI_OF_SECOND);
        // // First two digits of miliseconds
        // int msecH = milli / 10;
        // // Last digit of miliseconds
        // int msecL = milli % 10;

        if (year > 1999) {
            year -= 2000;
        }
        buffer[pos] = byteToBCD(year);
        buffer[pos + 1] = byteToBCD(month);
        buffer[pos + 2] = byteToBCD(day);
        buffer[pos + 3] = byteToBCD(hour);
        buffer[pos + 4] = byteToBCD(min);
        buffer[pos + 5] = byteToBCD(sec);
        buffer[pos + 6] = byteToBCD(0);
        buffer[pos + 7] = byteToBCD(0);
    }

    public static void setDIntAt(byte[] buffer, int pos, int value) {
        buffer[pos + 3] = (byte) (value & 0xFF);
        buffer[pos + 2] = (byte) ((value >> 8) & 0xFF);
        buffer[pos + 1] = (byte) ((value >> 16) & 0xFF);
        buffer[pos] = (byte) ((value >> 24) & 0xFF);
    }

    public static void setDWordAt(byte[] buffer, int pos, long value) {
        buffer[pos + 3] = (byte) (value & 0xFF);
        buffer[pos + 2] = (byte) ((value >> 8) & 0xFF);
        buffer[pos + 1] = (byte) ((value >> 16) & 0xFF);
        buffer[pos] = (byte) ((value >> 24) & 0xFF);
    }

    public static void setFloatAt(byte[] buffer, int pos, float value) {
        int DInt = Float.floatToIntBits(value);
        setDIntAt(buffer, pos, DInt);
    }

    public static void setShortAt(byte[] buffer, int pos, short value) {
        buffer[pos] = (byte) (value >> 8);
        buffer[pos + 1] = (byte) (value & 0x00FF);
    }

    public static void setWordAt(byte[] buffer, int pos, int value) {
        setShortAt(buffer, pos, (short) value);
    }

    public static void setSwapWordAt(byte[] buffer, int pos, int value) {
        buffer[pos + 1] = (byte) (value >> 8);
        buffer[pos] = (byte) (value & 0x00FF);
    }

    public static void setByteAt(byte[] buffer, int pos, byte b) {
        buffer[pos] = b;
    }

    public static void setBytesAt(byte[] buffer, int pos, byte[] bytes) {
        System.arraycopy(bytes, 0, buffer, pos, bytes.length);
    }

    public static void setStringAt(byte[] buffer, int pos, String value) {
        setStringAt(buffer, pos, value, StandardCharsets.UTF_8);
    }

    public static void setStringAt(byte[] buffer, int pos, String value, Charset charset) {
        setBytesAt(buffer, pos, value.getBytes(charset));
    }

    public static void setS7StringAt(byte[] buffer, int pos, int maxLen, String value) {
        byte[] data = value.getBytes(StandardCharsets.UTF_8);
        buffer[pos] = (byte) (maxLen & 0xFF);
        buffer[pos + 1] = (byte) (data.length & 0xFF);
        System.arraycopy(data, 0, buffer, pos + 2, Math.min(data.length, maxLen));
    }

    public static void hexDump(byte[] buffer, Consumer<String> c) {
        hexDump(buffer, 128, c);
    }

    public static void hexDump(byte[] buffer, int maxlines, Consumer<String> c) {
        hexDump(buffer, maxlines, 0, c);
    }

    public static void hexDump(byte[] buffer, int maxlines, int offset, Consumer<String> c) {
        if (buffer == null || buffer.length == 0 || maxlines < 1 || offset < 0) {
            return;
        }
        int length, pos;
        int line = 0;
        StringBuilder sb = new StringBuilder();
        while (line < maxlines && (pos = (line * 16) + offset) < buffer.length) {
            length = Math.min(16, buffer.length - pos);
            if (length < 1) {
                return;
            }
            sb.setLength(0);
            for (int i = 12; i >= 0; i -= 4) {
                sb.append(HEX_DIGIT[0x0F & line >>> i]);
            }
            sb.append('0').append(':').append(EMP_CHAR).append(EMP_CHAR);
            line++;
            for (int i = 0; i < 16; i++) {
                if (i < length) {
                    sb.append(HEX_DIGIT[0x0F & buffer[pos + i] >> 4]);
                    sb.append(HEX_DIGIT[0x0F & buffer[pos + i]]);
                } else {
                    sb.append(EMP_CHAR).append(EMP_CHAR);
                }
                sb.append(EMP_CHAR);
                if (i == 7) {
                    sb.append(EMP_CHAR);
                }
            }
            sb.append(EMP_CHAR);
            for (int i = 0; i < 16; i++) {
                if (i >= length) {
                    sb.append(EMP_CHAR);
                    continue;
                }
                char ch = (char) (buffer[pos + i] & 0xFF);
                if (Character.isISOControl(ch) || Character.isWhitespace(ch)) {
                    sb.append(DOT_CHAR);
                    continue;
                }
                sb.append(ch);
            }
            c.accept(sb.toString());
        }
    }

    public static DataItem buildDataItem(String address) {
        try {
            DataItem item;
            int i0 = address.indexOf('.');
            switch (address.charAt(0)) {
                case 'D':
                    if (i0 < 1){
                        throw new IllegalArgumentException(String.format("invalid DB address: %s", address));
                    }

                    int i1 = address.indexOf('.', i0 + 1);
                    int db = Integer.parseUnsignedInt(address.substring(2, i0));
                    DataType type;
                    switch (address.charAt(i0 + 3)) {
                        case 'X':
                            type = DataType.BIT;
                            break;
                        case 'B':
                            type = DataType.BYTE;
                            break;
                        case 'W':
                            type = DataType.WORD;
                            break;
                        case 'D':
                            type = DataType.DWORD;
                            break;
                        default:
                            throw new IllegalArgumentException(String.format("invalid DB address: %s", address));
                    }
                    int start = Integer.parseUnsignedInt(address.substring(i0 + 4, i1 > 0 ? i1 : address.length()));
                    item = new DataItem(AreaType.DB, type, db, start, i1 < 1 ? 0 : Integer.parseUnsignedInt(address.substring(i1 + 1, address.length())));
                    break;
                case 'M':
                    int startM = Integer.parseUnsignedInt(address.substring(1, i0 > 1 ? i0 : address.length()));
                    item = new DataItem(AreaType.MK, i0 > 1 ? DataType.BIT : DataType.BYTE, 0, startM, i0 < 1 ? 0 : Integer.parseUnsignedInt(address.substring(i0 + 1, address.length())));
                    break;
                default:
                    throw new IllegalArgumentException(String.format("invalid Memory address: %s", address));
            }
            return item;
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException(String.format("address: %s not parsable", address), e);
        }

    }


}
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;
import org.comtel2000.mokka7.block.BlockType;
import org.comtel2000.mokka7.block.PlcCpuStatus;
import org.comtel2000.mokka7.block.S7BlockInfo;
import org.comtel2000.mokka7.block.S7BlockList;
import org.comtel2000.mokka7.block.S7CpInfo;
import org.comtel2000.mokka7.block.S7CpuInfo;
import org.comtel2000.mokka7.block.S7DataItem;
import org.comtel2000.mokka7.block.S7OrderCode;
import org.comtel2000.mokka7.block.S7Protection;
import org.comtel2000.mokka7.block.S7Szl;
import org.comtel2000.mokka7.exception.S7Exception;
import org.comtel2000.mokka7.type.AreaType;
import org.comtel2000.mokka7.type.DataType;
import org.comtel2000.mokka7.util.S7;
/*
 * PROJECT Mokka7 (fork of Snap7/Moka7)
 *
 * Copyright (c) 2013,2016 Davide Nardella Copyright (c) 2017 J.Zimmermann (comtel2000)
 *
 * All rights reserved. This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Mokka7 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE whatever license you
 * decide to adopt.
 *
 * Contributors: Davide Nardella - initial API and implementation J.Zimmermann - Mokka7 fork
 *
 */
package org.comtel2000.mokka7;



/**
 *
 * @author comtel
 *
 */
public interface Client {

    final byte[] buffer = new byte[1024];

    /** Max number of vars (multiread/write) -> max PDU size */
    public static final int MAX_VARS = 20;

    /** Result transport size */
    static final byte TS_RESBIT = 0x03;
    static final byte TS_RESBYTE = 0x04;
    static final byte TS_RESINT = 0x05;
    static final byte TS_RESREAL = 0x07;
    static final byte TS_RESOCTET = 0x09;

    static final int DEFAULT_PDU_SIZE_REQUESTED = 480;

    static final int SIZE_RD = 31;
    static final int SIZE_WR = 35;

    /** default TCP receive timeout in ms */
    static final int RECV_TIMEOUT = 2000;

    /** default ISO tcp port */
    static final int ISO_TCP = 102;

    /** TPKT+COTP Header Size */
    static final int ISO_HEADER_SIZE = 7;

    static final int MAX_PDU_SIZE = DEFAULT_PDU_SIZE_REQUESTED + ISO_HEADER_SIZE;

    static final int MIN_PDU_SIZE = 16;

    /** TPKT + ISO COTP Header (Connection Oriented Transport Protocol) */
    static final byte[] TPKT_ISO = { // 7 bytes
            0x03, 0x00, 0x00, 0x1f, // Telegram Length (Data Size + 31 or 35)
            0x02, (byte) 0xf0, (byte) 0x80 // COTP (see above for info)
    };

    /** Telegrams ISO Connection Request telegram (contains also ISO Header and COTP Header) */
    static final byte ISO_CR[] = {
            // TPKT (RFC1006 Header)
            (byte) 0x03, // RFC 1006 ID (3)
            (byte) 0x00, // Reserved, always 0
            (byte) 0x00, // High part of packet lenght (entire frame, payload and TPDU included)
            (byte) 0x16, // Low part of packet lenght (entire frame, payload and TPDU included)
            // COTP (ISO 8073 Header)
            (byte) 0x11, // PDU Size length
            (byte) 0xE0, // CR - Connection Request ID
            (byte) 0x00, // Dst Reference HI
            (byte) 0x00, // Dst Reference LO
            (byte) 0x00, // Src Reference HI
            (byte) 0x01, // Src Reference LO
            (byte) 0x00, // Class + Options Flags
            (byte) 0xC0, // PDU Max length ID
            (byte) 0x01, // PDU Max length HI
            (byte) 0x0A, // PDU Max length LO
            (byte) 0xC1, // Src TSAP Identifier
            (byte) 0x02, // Src TSAP length (2 bytes)
            (byte) 0x01, // Src TSAP HI (will be overwritten)
            (byte) 0x00, // Src TSAP LO (will be overwritten)
            (byte) 0xC2, // Dst TSAP Identifier
            (byte) 0x02, // Dst TSAP length (2 bytes)
            (byte) 0x01, // Dst TSAP HI (will be overwritten)
            (byte) 0x02 // Dst TSAP LO (will be overwritten)
    };

    /** S7 get Block Info Request Header (contains also ISO Header and COTP Header) */
    static final byte S7_BI[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x25, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x07,
            (byte) 0x00, (byte) 0x00,
            (byte) 0x05, (byte) 0x00,
            (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x0c, (byte) 0x00, (byte) 0x01, (byte) 0x12,
            (byte) 0x04, (byte) 0x11, (byte) 0x43, (byte) 0x03, (byte) 0x00, (byte) 0xff, (byte) 0x09, (byte) 0x00, (byte) 0x08, (byte) 0x30, (byte) 0x41,
            // Block Type
            (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30,
            // ASCII Block Number
            (byte) 0x41 };

    /** S7 Clear Session Password */
    static final byte S7_CLR_PWD[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x1d, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x07,
            (byte) 0x00, (byte) 0x00, (byte) 0x29, (byte) 0x00, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x01, (byte) 0x12,
            (byte) 0x04, (byte) 0x11, (byte) 0x45, (byte) 0x02, (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00 };

    /** S7 COLD start request */
    static final byte S7_COLD_START[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x27, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x01,
            (byte) 0x00, (byte) 0x00, (byte) 0x0f, (byte) 0x00, (byte) 0x00, (byte) 0x16, (byte) 0x00, (byte) 0x00, (byte) 0x28, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfd, (byte) 0x00, (byte) 0x02, (byte) 0x43, (byte) 0x20, (byte) 0x09, (byte) 0x50,
            (byte) 0x5f, (byte) 0x50, (byte) 0x52, (byte) 0x4f, (byte) 0x47, (byte) 0x52, (byte) 0x41, (byte) 0x4d };

    /** get Date/Time request */
    static final byte S7_GET_DT[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x1d, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x07,
            (byte) 0x00, (byte) 0x00, (byte) 0x38, (byte) 0x00, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x01, (byte) 0x12,
            (byte) 0x04, (byte) 0x11, (byte) 0x47, (byte) 0x01, (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00 };

    /** S7 get PLC Status */
    static final byte S7_GET_STAT[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x21, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x07,
            (byte) 0x00, (byte) 0x00, (byte) 0x2c, (byte) 0x00, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x01, (byte) 0x12,
            (byte) 0x04, (byte) 0x11, (byte) 0x44, (byte) 0x01, (byte) 0x00, (byte) 0xff, (byte) 0x09, (byte) 0x00, (byte) 0x04, (byte) 0x04, (byte) 0x24,
            (byte) 0x00, (byte) 0x00 };

    /** S7 HOT start request */
    static final byte S7_HOT_START[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x25, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x01,
            (byte) 0x00, (byte) 0x00, (byte) 0x0c, (byte) 0x00, (byte) 0x00, (byte) 0x14, (byte) 0x00, (byte) 0x00, (byte) 0x28, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xfd, (byte) 0x00, (byte) 0x00, (byte) 0x09, (byte) 0x50, (byte) 0x5f, (byte) 0x50,
            (byte) 0x52, (byte) 0x4f, (byte) 0x47, (byte) 0x52, (byte) 0x41, (byte) 0x4d };

    /** S7 PDU Negotiation Telegram (contains also ISO Header and COTP Header) */
    static final byte S7_PN[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x19, (byte) 0x02, (byte) 0xf0,
            (byte) 0x80, // TPKT + COTP (see above for info)
            (byte) 0x32, (byte) 0x01,
            (byte) 0x00, (byte) 0x00, // Redundancy identification
            (byte) 0xff, (byte) 0xff, // Protocol Data Unit Reference (init)
            (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0xf0,
            (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x00,

            (byte) 0x1e // PDU length Requested = HI-LO 480 bytes
    };

    /** S7 Read/Write Request Header (contains also ISO Header and COTP Header) */
    static final byte S7_RW[] = { // 31-35 bytes
            (byte) 0x03, (byte) 0x00, (byte) 0x00,

            (byte) 0x1f, // Telegram length (Data Size + 31 or 35)
            (byte) 0x02, (byte) 0xf0, (byte) 0x80, // COTP (see above for info)
            (byte) 0x32, // S7 Protocol ID
            (byte) 0x01, // Job Type
            (byte) 0x00, (byte) 0x00, // Redundancy identification
            (byte) 0x00, (byte) 0x00, // Protocol Data Unit Reference
            (byte) 0x00, (byte) 0x0e, // Parameters length
            (byte) 0x00, (byte) 0x00, // Data length = Size(bytes) + 4
            (byte) 0x04, // Function 4 Read Var, 5 Write Var
            (byte) 0x01, // Items count
            (byte) 0x12, // Var spec.
            (byte) 0x0a, // length of remaining bytes
            (byte) 0x10, // Syntax ID
            DataType.BYTE.getValue(), // Transport Size
            (byte) 0x00, (byte) 0x00, // Num Elements
            (byte) 0x00, (byte) 0x00, // DB Number (if any, else 0)
            (byte) 0x84, // area Type
            (byte) 0x00, (byte) 0x00, (byte) 0x00, // area offset
            // WR area
            (byte) 0x00, // Reserved
            (byte) 0x04, // Transport size
            (byte) 0x00, (byte) 0x00, // Data length * 8 (if not timer or counter)
    };

    /** get Date/Time command */
    static final byte S7_SET_DT[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x27, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x07,
            (byte) 0x00, (byte) 0x00, (byte) 0x89, (byte) 0x03, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x0e, (byte) 0x00, (byte) 0x01, (byte) 0x12,
            (byte) 0x04, (byte) 0x11, (byte) 0x47, (byte) 0x02, (byte) 0x00, (byte) 0xff, (byte) 0x09, (byte) 0x00, (byte) 0x0a, (byte) 0x00,

            (byte) 0x19, // Hi part of Year
            (byte) 0x13, // Lo part of Year
            (byte) 0x12, // Month
            (byte) 0x06, // Day
            (byte) 0x17, // Hour
            (byte) 0x37, // Min
            (byte) 0x13, // Sec
            (byte) 0x00, (byte) 0x01 // ms + Day of week
    };

    /** S7 Set Session Password */
    static final byte S7_SET_PWD[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x25, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x07,
            (byte) 0x00, (byte) 0x00, // Redundancy identification
            (byte) 0x27, (byte) 0x00, // Protocol Data Unit Reference
            (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x0c, (byte) 0x00, (byte) 0x01, (byte) 0x12,
            (byte) 0x04, (byte) 0x11, (byte) 0x45, (byte) 0x01, (byte) 0x00, (byte) 0xff, (byte) 0x09, (byte) 0x00, (byte) 0x08,
            // 8 Char Encoded Password
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 };

    /** S7 STOP request */
    static final byte S7_STOP[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x21, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x01,
            (byte) 0x00, (byte) 0x00,  // Redundancy identification
            (byte) 0x0e, (byte) 0x00,  // Protocol Data Unit Reference
            (byte) 0x00, (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x29, (byte) 0x00, (byte) 0x00,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x09, (byte) 0x50, (byte) 0x5f, (byte) 0x50, (byte) 0x52, (byte) 0x4f, (byte) 0x47, (byte) 0x52,
            (byte) 0x41, (byte) 0x4d };

    /** SZL First telegram request */
    static final byte S7_SZL_FIRST[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x21, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x07,
            (byte) 0x00, (byte) 0x00,  // Redundancy identification
            (byte) 0x00, (byte) 0x00,  // Protocol Data Unit Reference
            (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x01, (byte) 0x12, (byte) 0x04, (byte) 0x11, (byte) 0x44, (byte) 0x01,
            (byte) 0x00, (byte) 0xff, (byte) 0x09, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00, // ID
                                                                                                       // (29)
            (byte) 0x00, (byte) 0x00 // Index (31)
    };

    /** SZL Next telegram request */
    static final byte S7_SZL_NEXT[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x21, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x07,
            (byte) 0x00, (byte) 0x00,  // Redundancy identification
            (byte) 0x00, (byte) 0x00,  // Protocol Data Unit Reference
            (byte) 0x00, (byte) 0x0c, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x01, (byte) 0x12,
            (byte) 0x08, (byte) 0x12, (byte) 0x44, (byte) 0x01, (byte) 0x01, // Sequence
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00 };

    /** S7 Variable MultiRead Header */
    static final byte[] S7_MRD_HEADER = { 0x03, 0x00, 0x00, 0x1f, // Telegram length
            0x02, (byte) 0xf0, (byte) 0x80, // COTP (see above for info)
            0x32, // S7 Protocol ID
            0x01, // Job Type
            0x00, 0x00, // Redundancy identification
            0x00, 0x00, // Protocol Data Unit Reference
            0x00, 0x0e, // Parameters length
            0x00, 0x00, // Data length = Size(bytes) + 4
            0x04, // Function 4 Read Var, 5 Write Var
            0x01 // Items count (idx 18)
    };

    /** S7 Variable MultiRead Item */
    static final byte[] S7_MRD_ITEM = { 0x12, // Var spec.
            0x0a, // length of remaining bytes
            0x10, // Syntax ID
            DataType.BYTE.getValue(), // Transport Size idx=3
            0x00, 0x00, // Num Elements
            0x00, 0x00, // DB Number (if any, else 0)
            (byte) 0x84, // area Type
            0x00, 0x00, 0x00 // area offset
    };

    /** S7 Variable MultiWrite Header */
    static final byte[] S7_MWR_HEADER = { 0x03, 0x00, 0x00, 0x1f, // Telegram length
            0x02, (byte) 0xf0, (byte) 0x80, // COTP (see above for info)
            0x32, // S7 Protocol ID
            0x01, // Job Type
            0x00, 0x00, // Redundancy identification
            0x00, 0x00, // Protocol Data Unit Reference
            0x00, 0x0e, // Parameters length (idx 13)
            0x00, 0x00, // Data length = Size(bytes) + 4 (idx 15)
            0x05, // Function 5 Write Var
            0x01 // Items count (idx 18)
    };

    /** S7 Variable MultiWrite Item (Param) */
    static final byte[] S7_MWR_PARAM = { 0x12, // Var spec.
            0x0a, // length of remaining bytes
            0x10, // Syntax ID
            DataType.BYTE.getValue(), // Transport Size idx=3
            0x00, 0x00, // Num Elements
            0x00, 0x00, // DB Number (if any, else 0)
            (byte) 0x84, // area Type
            0x00, 0x00, 0x00, // area offset
    };

    /** S7 get Block List */
    static final byte S7_BL[] = { (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x1d, (byte) 0x02, (byte) 0xf0, (byte) 0x80, (byte) 0x32, (byte) 0x07,
            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, // [11,12] Protocol Data Unit Reference
            (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x01, (byte) 0x12, (byte) 0x04, (byte) 0x11, (byte) 0x43, (byte) 0x01,
            (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00 };

    boolean clearSessionPassword() throws S7Exception;

    /**
     * Connects the client to the hardware at (IP, Rack, Slot) Coordinates. Connects the client to
     * the PLC with the parameters specified in the previous call of
     * {@link #setConnectionParams(String, int, int)}.
     *
     * @see #connect(String, int, int)
     * @return succeed
     * @throws S7Exception ex
     */
    boolean connect() throws S7Exception;

    /**
     * Returns connection state
     *
     * @return connected
     */
    boolean isConnected();

    /**
     * Connects the client to the hardware at (IP, Rack, Slot) Coordinates.
     *
     * <table summary="PLC settings">
     * <tr>
     * <th>PLC</th>
     * <th>Rack</th>
     * <th>Slot</th>
     * <th>Options</th>
     * </tr>
     * <tr>
     * <td>S7 300 CPU</td>
     * <td>0</td>
     * <td>2</td>
     * <td>Always</td>
     * </tr>
     * <tr>
     * <td>S7 400 CPU</td>
     * <td>0..X</td>
     * <td>0..X</td>
     * <td>Follow the hardware configuration</td>
     * </tr>
     * <tr>
     * <td>S7 1200 CPU</td>
     * <td>0</td>
     * <td>0</td>
     * <td>Or 0, 1</td>
     * </tr>
     * <tr>
     * <td>S7 1500 CPU</td>
     * <td>0</td>
     * <td>0</td>
     * <td>Or 0, 1</td>
     * </tr>
     * <tr>
     * <td>WinAC CPU</td>
     * <td>0..X</td>
     * <td>0..X</td>
     * <td>Follow the hardware configuration</td>
     * </tr>
     * <tr>
     * <td>WinAC IE</td>
     * <td>0</td>
     * <td>0</td>
     * <td>Or follow the hardware configuration</td>
     * </tr>
     * </table>
     *
     * @param address host or ip address
     * @param rack Rack number
     * @param slot Slot number
     * @return succeed
     * @throws S7Exception ex
     */
    boolean connect(String address, int rack, int slot) throws S7Exception;

    /**
     * Read an entire DB from the PLC without the need of specifying its size. As output will
     * contain the size read.
     *
     * @param db DB number
     * @param buffer buffer to fill
     * @return the size readed
     * @throws S7Exception ex
     */
    int dbGet(int db, byte[] buffer) throws S7Exception;

    /**
     * Fill the entire DB from the PLC with a fill byte parameter. As output will contain the size
     * written.
     *
     * @param db DB number
     * @param fill char/byte to fill with
     * @return the written byte count
     * @throws S7Exception ex
     */
    int dbFill(int db, byte fill) throws S7Exception;

    /**
     * Disconnects "gracefully" the Client from the PLC.
     */
    void disconnect();

    /**
     * Returns some information about a given block. This function is very useful if you need to
     * read or write data in a DB which you do not know the size in advance (see MC7Size field).
     *
     * @param type of Block that we need
     * @param blockNumber Number of Block
     * @return {@link S7BlockInfo} info
     * @throws S7Exception ex
     */
    S7BlockInfo getAgBlockInfo(BlockType type, int blockNumber) throws S7Exception;

    S7BlockList getS7BlockList() throws S7Exception;

    S7CpInfo getCpInfo() throws S7Exception;

    S7CpuInfo getCpuInfo() throws S7Exception;

    S7OrderCode getOrderCode() throws S7Exception;

    LocalDateTime getPlcDateTime() throws S7Exception;

    PlcCpuStatus getPlcStatus() throws S7Exception;