
package uk.co.wingpath.modbus;

import java.io.*;
import uk.co.wingpath.io.*;
import uk.co.wingpath.util.*;

/**
* This class provides communication channels over which Modbus messages
* may be sent and received.
* <p>Legacy class - now only supports slave end.
* <p>The {@link ModbusServiceFactory} class is recommended for handling the
* slave end of a connection. The run method in that class contains the server
* loop, and provides proper coordination between the request and the response.
* <p>Any code that still uses this class should be checked against the code
* in {@link ModbusServiceFactory} to ensure that it does all the correct
* checks and tracing.
*/
@Deprecated
public class ModbusChannel
{
    private final Connection connection;
    private final PacketType packetType;
    private Tracer tracer;
    private boolean alwaysRespond;
    private final Reporter reporter;

    /**
    * Constructs a {@code ModbusChannel} using the supplied
    * {@code Connection} and {@code PacketType}.
    * @param connection the connection.
    * @param packetType the packet type.
    * @param alwaysRespond whether response should be sent to broadcast request,
    * and exception responses 10/11 should be used. Normally {@code true} for
    * socket connection, and {@code false} for serial connection.
    * @param reporter for reporting recoverable errors.
    */
    public ModbusChannel (Connection connection, PacketType packetType,
        boolean alwaysRespond, Reporter reporter)
    {
        this.connection = connection;
        this.packetType = packetType;
        this.alwaysRespond = alwaysRespond;
        this.reporter = reporter == null ? new DummyReporter () :
            reporter;
    }

    /**
    * Sets the tracer to be used for tracing messages.
    * @param tracer the tracer.
    */
    public void setTracer (Tracer tracer)
    {
        this.tracer = tracer;
    }

    /**
    * Traces and discards any buffered input, attempting to parse it as
    * Modbus requests.
    * Note that in an RS485 multi-drop setup, we may also receive requests
    * and responses to/from other slaves.
    */
    private void discardInput (Connection connection)
        throws InterruptedException
    {
        for (;;)
        {
            try
            {
                packetType.setTimeout (0);
                ModbusMessage request = packetType.receiveRequest (connection,
                    tracer, null);
                ModbusMessage.traceDiscard (tracer, "S401", "Unexpected");
            }
            catch (IOException e)
            {
                break;
            }
            finally
            {
                packetType.setTimeout (1000);
            }
        }
    }

    /**
    * Receives a Modbus request message.
    * @return The received message.
    * @throws InterruptedException if the thread is interrupted.
    * @throws IOException if an I/O error occurs.
    * @throws InterruptedIOException if a timeout occurs.
    */
    public ModbusTransaction receiveRequest ()
        throws InterruptedException, IOException, InterruptedIOException
    {
        ModbusMessage request = packetType.receiveRequest (connection,
            tracer, null);
        return new ModbusTransaction (request, reporter, null);
    }

    /**
    * Sends a Modbus response message.
    * <p>The packet type is called to format the message and write it over
    * the connection.
    * <p>The message is not sent if the packet type is not TCP and the message
    * is a response to a broadcast or a TCP-specific error response.
    * @param trans the transaction whose response is to be sent.
    * @throws InterruptedException if the thread is interrupted.
    * @throws IOException if an I/O error occurs.
    */
    public void send (ModbusTransaction trans)
        throws InterruptedException, IOException
    {
        if (!packetType.hasTransactionIds ())
        {
            // Discard any unexpected input - this is done for
            // diagnostic reasons. The most likely cause of
            // unexpected input is that the master has timed out
            // waiting for a response from us. In a production system
            // the best way to handle this would probably be to NOT
            // send the response that we are about to send, and deal
            // with the new request instead. In a test program, it's
            // simpler and hopefully more informative to trace and
            // discard the new request.
            // None of this applies to Modbus/TCP, where the master
            // may be "pipelining" requests.
            discardInput (connection);
        }

        ModbusMessage response = trans.getResponse ();

        // If not using TCP, don't send reply to broadcast and don't
        // send 0x0A or 0x0B error responses.

        if (!alwaysRespond)
        {
            if (response.getSlaveId () == 0)
            {
                if (response.isError ())
                {
                    if (response.getErrorCode () != Modbus.ERROR_TIMED_OUT)
                        response.traceInterpretation ();
                }
                response.traceExplanation ("S402",
                    "Response not sent: Request was broadcast");
                if (tracer != null)
                    tracer.endTransaction ();
                return;
            }
            if (response.isError ())
            {
                int error = response.getErrorCode ();
                if (error == Modbus.ERROR_PATH_UNAVAILABLE ||
                    error == Modbus.ERROR_TIMED_OUT)
                {
                    response.traceInterpretation ();
                    response.traceExplanation ("S403",
                        "Response not sent: Exception 10/11");
                    if (tracer != null)
                        tracer.endTransaction ();
                    return;
                }
            }
        }

        packetType.send (connection, response);
        if (tracer != null)
            tracer.endTransaction ();
    }
}



