
package uk.co.wingpath.modbus;

/**
* This class defines Modbus constants and some associated methods.
* <p>In particular, constants are defined for Modbus function codes and
* error codes, and static methods are provided to convert these constants
* to descriptive strings.
*/
public class Modbus
{
    private Modbus () {}

    /**
    * Official maximum number of bytes in a "Protocol Data Unit".
    * The PDU includes the function code and following fields (but
    * excludes any CRC/LRC).
    */
    public static final int MAX_PDU_SIZE = 253;

    /**
    * Implementation limit on number of bytes in a "Protocol Data Unit"
    * (i.e. determined by buffer sizes).
    * The PDU includes the function code and following fields (but
    * excludes any CRC/LRC).
    * <p>The value 65534 is actually the largest PDU that can be carried by the
    * TCP packet type.
    */
    public static final int MAX_IMP_PDU_SIZE = 65534;

    /**
    * Minimum sensible value for maximum PDU size.
    */
    public static final int MIN_PDU_SIZE = 20;

    /**
    * Official maximum number of data bytes that can be read in a single
    * request (using functions 1, 2, 3, 4).
    * <p>This is currently 250 bytes, which is equivalent to 125 registers
    * or 2000 coils/discrete-inputs.
    * <p>Functions 1,2,3,4 responses have an overhead of 2 bytes, so the maximum
    * of data bytes would give a PDU size of 250 + 2 = 252 bytes, which is
    * 1 less than the maximumu PDU size (253 bytes).
    */
    public static final int MAX_READ_BYTES = 250;

    /**
    * Official maximum number of data bytes that can be written in a single
    * request (using functions 15, 16).
    * <p>This is currently 246 bytes, which is equivalent to 123 registers
    * or 1968 coils/discrete-inputs.
    * <p>Function 15 and 16 requests have an overhead of 6 bytes, so the maximum
    * of data bytes would give a PDU size of 246 + 6 = 252 bytes, which is
    * 1 less than the maximumu PDU size (253 bytes).
    */
    public static final int MAX_WRITE_BYTES = 246;

    /** Function code. */
    public static final int FUNC_READ_COILS = 1;
    /** Function code. */
    public static final int FUNC_READ_DISCRETE_INPUTS = 2;
    /** Function code. */
    public static final int FUNC_READ_HOLDING_REGISTERS = 3;
    /** Function code. */
    public static final int FUNC_READ_INPUT_REGISTERS = 4;
    /** Function code. */
    public static final int FUNC_WRITE_SINGLE_COIL = 5;
    /** Function code. */
    public static final int FUNC_WRITE_SINGLE_REGISTER = 6;
    /** Function code. */
    public static final int FUNC_READ_EXCEPTION_STATUS = 7;
    /** Function code. */
    public static final int FUNC_DIAGNOSTICS = 8;
    /** Function code. */
    public static final int FUNC_GET_COMM_EVENT_COUNTER = 11;
    /** Function code. */
    public static final int FUNC_GET_COMM_EVENT_LOG = 12;
    /** Function code. */
    public static final int FUNC_WRITE_MULTIPLE_COILS = 15;
    /** Function code. */
    public static final int FUNC_WRITE_MULTIPLE_REGISTERS = 16;
    /** Function code. */
    public static final int FUNC_REPORT_SLAVE_ID = 17;
    /** Function code. */
    public static final int FUNC_READ_FILE_RECORD = 20;
    /** Function code. */
    public static final int FUNC_WRITE_FILE_RECORD = 21;
    /** Function code. */
    public static final int FUNC_MASK_WRITE_REGISTER = 22;
    /** Function code. */
    public static final int FUNC_READ_WRITE_MULTIPLE_REGISTERS = 23;
    /** Function code. */
    public static final int FUNC_READ_FIFO_QUEUE = 24;
    /** Function code. */
    public static final int FUNC_ENCAPSULATED_INTERFACE_TRANSPORT = 43;
    /** Function code. */
    public static final int FUNC_MONITOR_INPUT_REGISTERS = 65;
    /** Function code. */
    public static final int FUNC_MONITOR_DISCRETE_INPUTS = 66;
    /** Function code. */
    public static final int FUNC_MONITOR_HOLDING_REGISTERS = 67;
    /** Function code. */
    public static final int FUNC_MONITOR_COILS = 68;
    /** Function code. */
    public static final int FUNC_MONITOR_FILE_RECORDS = 69;

    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_QUERY_DATA = 0;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RESTART_COMM_OPTION = 1;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_DIAGNOSTIC_REGISTER = 2;
    /** Diagnostic sub-function code. */
    public static final int DIAG_CHANGE_ASCII_INPUT_DELIMITER = 3;
    /** Diagnostic sub-function code. */
    public static final int DIAG_FORCE_LISTEN_ONLY_MODE = 4;
    /** Diagnostic sub-function code. */
    public static final int DIAG_CLEAR_COUNTERS = 10;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_BUS_MESSAGE_COUNT = 11;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_BUS_COMM_ERROR_COUNT = 12;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_BUS_EXCEPTION_COUNT = 13;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_SLAVE_MESSAGE_COUNT = 14;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_SLAVE_NO_RESPONSE_COUNT = 15;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_SLAVE_NAK_COUNT = 16;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_SLAVE_BUSY_COUNT = 17;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_BUS_CHARACTER_OVERRUN_COUNT = 18;
    /** Diagnostic sub-function code. */
    public static final int DIAG_RETURN_OVERRUN_ERROR_COUNT = 19;
    /** Diagnostic sub-function code. */
    public static final int DIAG_CLEAR_OVERRUN_COUNTER = 20;
    /** Diagnostic sub-function code. */
    public static final int DIAG_GET_CLEAR_MODBUS_PLUS_STATISTICS = 21;

    public static final int COMM_EVENT_RCV_OK = 0x80;
    public static final int COMM_EVENT_RCV_COMM_ERROR = 0x80|0x02;
    public static final int COMM_EVENT_RCV_OVERRUN = 0x80|0x10;
    public static final int COMM_EVENT_RCV_LISTEN_ONLY = 0x80|0x20;
    public static final int COMM_EVENT_RCV_BROADCAST = 0x80|0x40;
    public static final int COMM_EVENT_SND_OK = 0x40;
    public static final int COMM_EVENT_SND_EXCEPTION = 0x40|0x01;
    public static final int COMM_EVENT_SND_ABORT = 0x40|0x02;
    public static final int COMM_EVENT_SND_BUSY = 0x40|0x04;
    public static final int COMM_EVENT_SND_NAK = 0x40|0x08;
    public static final int COMM_EVENT_SND_TIMEOUT = 0x40|0x08;
    public static final int COMM_EVENT_SND_LISTEN_ONLY = 0x40|0x08;
    public static final int COMM_EVENT_LISTEN_ONLY = 0x04;
    public static final int COMM_EVENT_RESTART = 0x00;

    /** Exception code. */
    public static final int ERROR_NONE = 0;
    /** Exception code. */
    public static final int ERROR_ILLEGAL_FUNCTION = 1;
    /** Exception code. */
    public static final int ERROR_ILLEGAL_DATA_ADDRESS = 2;
    /** Exception code. */
    public static final int ERROR_ILLEGAL_DATA_VALUE = 3;
    /** Exception code. */
    public static final int ERROR_SERVER_FAILURE = 4;
    /** Exception code. */
    public static final int ERROR_ACKNOWLEDGE = 5;
    /** Exception code. */
    public static final int ERROR_SERVER_BUSY = 6;
    /** Exception code. */
    public static final int ERROR_MEMORY_PARITY = 8;
    /** Exception code. */
    public static final int ERROR_PATH_UNAVAILABLE = 0x0a;
    /** Exception code. */
    public static final int ERROR_TIMED_OUT = 0x0b;

    /** Device Identification Object Id */
    public static final int DEVICE_VENDOR_NAME = 0x00;
    /** Device Identification Object Id */
    public static final int DEVICE_PRODUCT_CODE = 0x01;
    /** Device Identification Object Id */
    public static final int DEVICE_MAJOR_MINOR_REVISION = 0x02;
    /** Device Identification Object Id */
    public static final int DEVICE_VENDOR_URL = 0x03;
    /** Device Identification Object Id */
    public static final int DEVICE_PRODUCT_NAME = 0x04;
    /** Device Identification Object Id */
    public static final int DEVICE_MODEL_NAME = 0x05;
    /** Device Identification Object Id */
    public static final int DEVICE_USER_APPLICATION_NAME = 0x06;
    /** Device Identification Object Id */
    public static final int DEVICE_MIN_RESERVED = 0x07;
    /** Device Identification Object Id */
    public static final int DEVICE_MAX_RESERVED = 0x7f;
    /** Device Identification Object Id */
    public static final int DEVICE_MIN_PRIVATE = 0x80;
    /** Device Identification Object Id */
    public static final int DEVICE_MAX_PRIVATE = 0xff;

    /** Encapsulated Interface (MEI) Transport types */
    public static final int MEI_CANOPEN = 0x0d;
    public static final int MEI_READ_DEVICE_IDENTIFICATION = 0x0e;

    /**
    * Returns string describing Modbus function code
    * @param code the function code
    * @return descriptive string, or null if non-standard function
    */
    public static String getFunctionName (int code)
    {
        String str;

        switch (code & 0x7f)
        {
        default:
            str = null;
            break;
    	case FUNC_READ_COILS:
    		str = "Read Coils";
			break;
    	case FUNC_READ_DISCRETE_INPUTS:
    		str = "Read Discrete Inputs";
			break;
    	case FUNC_READ_HOLDING_REGISTERS:
    		str = "Read Holding Registers";
			break;
    	case FUNC_READ_INPUT_REGISTERS:
    		str = "Read Input Registers";
			break;
    	case FUNC_WRITE_SINGLE_COIL:
    		str = "Write Single Coil";
			break;
    	case FUNC_WRITE_SINGLE_REGISTER:
    		str = "Write Single Register";
			break;
    	case FUNC_WRITE_MULTIPLE_COILS:
    		str = "Write Multiple Coils";
			break;
    	case FUNC_WRITE_MULTIPLE_REGISTERS:
    		str = "Write Multiple Registers";
			break;
    	case FUNC_READ_FILE_RECORD:
    		str = "Read File Record";
			break;
    	case FUNC_WRITE_FILE_RECORD:
    		str = "Write File Record";
			break;
    	case FUNC_MASK_WRITE_REGISTER:
    		str = "Mask Write Register";
			break;
    	case FUNC_READ_WRITE_MULTIPLE_REGISTERS:
    		str = "Read/Write Multiple Registers";
            break;
    	case FUNC_ENCAPSULATED_INTERFACE_TRANSPORT:
    		str = "MEI";
			break;
        case FUNC_READ_EXCEPTION_STATUS:
            str = "Read Exception Status";
            break;
        case FUNC_DIAGNOSTICS:
            str = "Diagnostics";
            break;
        case FUNC_GET_COMM_EVENT_COUNTER:
            str = "Get Comm Event Counter";
            break;
        case FUNC_GET_COMM_EVENT_LOG:
            str = "Get Comm Event Log";
            break;
        case FUNC_REPORT_SLAVE_ID:
            str = "Report Slave ID";
            break;
        case FUNC_READ_FIFO_QUEUE:
            str = "Read FIFO Queue";
            break;
        }
        return str;
    }

    /**
    * Returns string describing Modbus diagnostics sub-function code
    * @param code the sub-function code
    * @return descriptive string, or null if non-standard sub-function
    */
    public static String getDiagFunctionName (int code)
    {
        switch (code)
        {
        case DIAG_RETURN_QUERY_DATA:
            return "Return Query Data";
        case DIAG_RESTART_COMM_OPTION:
            return "Restart Comm Option";
        case DIAG_RETURN_DIAGNOSTIC_REGISTER:
            return "Return Diagnostic Register";
        case DIAG_CHANGE_ASCII_INPUT_DELIMITER:
            return "Change ASCII Input Delimiter";
        case DIAG_FORCE_LISTEN_ONLY_MODE:
            return "Force Listen Only Mode";
        case DIAG_CLEAR_COUNTERS:
            return "Clear Counters";
        case DIAG_RETURN_BUS_MESSAGE_COUNT:
            return "Return Bus Message Count";
        case DIAG_RETURN_BUS_COMM_ERROR_COUNT:
            return "Return Bus Comm Error Count";
        case DIAG_RETURN_BUS_EXCEPTION_COUNT:
            return "Return Bus Exception Count";
        case DIAG_RETURN_SLAVE_MESSAGE_COUNT:
            return "Return Slave Message Count";
        case DIAG_RETURN_SLAVE_NO_RESPONSE_COUNT:
            return "Return Slave No Response Count";
        case DIAG_RETURN_SLAVE_NAK_COUNT:
            return "Return Slave NAK Count";
        case DIAG_RETURN_SLAVE_BUSY_COUNT:
            return "Return Slave Busy Count";
        case DIAG_RETURN_BUS_CHARACTER_OVERRUN_COUNT:
            return "Return Bus Character Overrun Count";
        case DIAG_RETURN_OVERRUN_ERROR_COUNT:
            return "Return Overrun Error Count";
        case DIAG_CLEAR_OVERRUN_COUNTER:
            return "Clear Overrun Counter";
        case DIAG_GET_CLEAR_MODBUS_PLUS_STATISTICS:
            return "Get/Clear Modbus Plus Statistics";
        }
        return null;
    }

    /**
    * Returns string describing Modbus Encapsulated Interface Transport
    * type code.
    * @param code the type code.
    * @return descriptive string, or null if non-standard type code.
    */
    public static String getEitTypeName (int code)
    {
        switch (code)
        {
        case MEI_CANOPEN:
            return "CANopen General Reference";
        case MEI_READ_DEVICE_IDENTIFICATION:
            return "Read Device Identification";
        }
        return null;
    }

    /**
    * Returns error message corresponding to Modbus error code
    * @param code the error code
    * @return corresponding error message
    */
    public static String getErrorMessage (int code)
    {
        String str;

        switch (code)
        {
        default:
            str = "Error code " + code;
            break;
        case ERROR_NONE:
            str = "OK";
            break;
        case ERROR_ILLEGAL_FUNCTION:
            str = "Illegal function";
            break;
        case ERROR_ILLEGAL_DATA_ADDRESS:
            str = "Illegal data address";
            break;
        case ERROR_ILLEGAL_DATA_VALUE:
            str = "Illegal data value";
            break;
        case ERROR_SERVER_FAILURE:
            str = "Server failure";
            break;
        case ERROR_ACKNOWLEDGE:
            str = "Acknowledge";
            break;
        case ERROR_SERVER_BUSY:
            str = "Server busy";
            break;
        case ERROR_MEMORY_PARITY:
            str = "Memory parity error";
            break;
        case ERROR_PATH_UNAVAILABLE:
            str = "No path available to target";
            break;
        case ERROR_TIMED_OUT:
            str = "Target device failed to respond";
            break;
        }
        return str;
    }

    /** Gets a help ID for the specified error code.
    * The help ID is "MEnn", where 'nn' is the zero-padded 2-digit
    * error code in decimal, or "MEXX" if the error code is invalid,
    * or {@code null} if the error code is ERROR_NONE.
    * <p>The help ID is intended for use in master-mode only.
    * Slave-mode uses a larger set of help IDs, to distinguish errors that
    * share the same error code.
    * @param code the error code.
    * @return the help ID for the error code.
    */
    static String getHelpId (int code)
    {
        switch (code)
        {
        default:
            return "MEXX";

        case ERROR_NONE:
            return null;

        case ERROR_ILLEGAL_FUNCTION:
        case ERROR_ILLEGAL_DATA_ADDRESS:
        case ERROR_ILLEGAL_DATA_VALUE:
        case ERROR_SERVER_FAILURE:
        case ERROR_ACKNOWLEDGE:
        case ERROR_SERVER_BUSY:
        case ERROR_MEMORY_PARITY:
        case ERROR_PATH_UNAVAILABLE:
        case ERROR_TIMED_OUT:
            return String.format ("ME%02d", code);
        }
    }

    /**
    * Throws an illegal-address Modbus exception.
    * @param helpId help identifier.
    * @param msg exception message.
    * @throws ModbusException
    */
    public static void addressError (String helpId, String msg)
        throws ModbusException
    {
        throw new ModbusException (ERROR_ILLEGAL_DATA_ADDRESS, helpId, msg);
    }

    /**
    * Throws an illegal-data Modbus exception.
    * @param helpId help identifier.
    * @param msg exception message.
    * @throws ModbusException
    */
    public static void dataError (String helpId, String msg)
        throws ModbusException
    {
        throw new ModbusException (ERROR_ILLEGAL_DATA_VALUE, helpId, msg);
    }
}


