001package jmri.jmrix.direct;
002
003import java.io.DataInputStream;
004import java.io.IOException;
005import java.io.OutputStream;
006import jmri.jmrix.AbstractSerialPortController;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Converts Stream-based I/O to/from NMRA packets and controls sending to the
012 * direct interface.
013 * <p>
014 * This is much simpler than many other "TrafficHandler" classes, because
015 *   <ul>
016 *   <li>It's not handling mode information, or even any information back from the
017 *   device; it's just sending.
018 *   <li>It can work with the direct packets.
019 *   </ul>
020 * This actually bears more similarity to a pure implementation of the
021 * CommandStation interface, which is where the real guts of it is. In
022 * particular, note that transmission is not a threaded operation.
023 *
024 * @author Bob Jacobsen Copyright (C) 2001
025 */
026public class TrafficController implements jmri.CommandStation {
027
028    /**
029     * Create a new Direct TrafficController instance.
030     * @param memo system connection.
031     */
032    public TrafficController(DirectSystemConnectionMemo memo) {
033        super();
034        _memo = memo;
035    }
036
037    /**
038     * Send a specific packet to the rails.
039     *
040     * @param packet  Byte array representing the packet, including the
041     *                error-correction byte. Must not be null.
042     * @param repeats Number of times to repeat the transmission, but is ignored
043     *                in the current implementation
044     */
045    @Override
046    public boolean sendPacket(byte[] packet, int repeats) {
047
048        if (repeats != 1) {
049            log.warn("Only single transmissions currently available");
050        }
051
052        // convert packet (in byte form) into bits
053        int[] msgAsInt = MakePacket.createStream(packet);
054
055        if (msgAsInt[0] == 0) {
056            // failed to make packet
057            log.error("Failed to convert packet to transmitable form: {}", java.util.Arrays.toString(packet));
058            return false;
059        }
060
061        // have to recopy & reformat, as there's only a byte write in Java 1
062        // note that msgAsInt has 0th byte containing length still
063        byte[] msg = new byte[msgAsInt[0]];
064        for (int i = 0; i < msg.length; i++) {
065            msg[i] = (byte) (msgAsInt[i + 1] & 0xFF);
066        }
067
068        // and stream the resulting byte array
069        try {
070            if (ostream != null) {
071                if (log.isDebugEnabled()) {
072                    StringBuilder f = new StringBuilder();
073                    for (int i = 0; i < msg.length; i++) {
074                        f.append(Integer.toHexString(0xFF & msg[i])).append(" ");
075                    }
076                    log.debug("write message: {}", f);
077                }
078                ostream.write(msg);
079            } else {
080                // no stream connected
081                log.warn("sendMessage: no connection established");
082            }
083        } catch (IOException e) {
084            log.warn("sendMessage: Exception: {}", e.getMessage());
085        }
086        return true;
087    }
088
089    // methods to connect/disconnect to a source of data in an AbstractSerialPortController
090    private DirectSystemConnectionMemo _memo = null;
091    private AbstractSerialPortController controller = null;
092
093    public boolean status() {
094        return (ostream != null && istream != null);
095    }
096
097    /**
098     * Make connection to existing PortController object.
099     *
100     * @param p the controller to connect to
101     */
102    public void connectPort(AbstractSerialPortController p) {
103        istream = p.getInputStream();
104        ostream = p.getOutputStream();
105        if (controller != null) {
106            log.warn("connectPort: connect called while connected");
107        } else {
108            log.debug("connectPort invoked");
109        }
110        controller = p;
111    }
112
113    /**
114     * Break connection to existing PortController object. Once broken, attempts
115     * to send via "message" member will fail.
116     *
117     * @param p the controller to disconnect from
118     */
119    public void disconnectPort(AbstractSerialPortController p) {
120        istream = null;
121        ostream = null;
122        if (controller != p) {
123            log.warn("disconnectPort: disconnect called from non-connected AbstractPortController");
124        }
125        controller = null;
126    }
127
128    // data members to hold the streams
129    protected DataInputStream istream = null;
130    protected OutputStream ostream = null;
131
132    @Override
133    public String getUserName() {
134        return _memo.getUserName();
135    }
136
137    @Override
138    public String getSystemPrefix() {
139        return _memo.getSystemPrefix();
140    }
141
142    private final static Logger log = LoggerFactory.getLogger(TrafficController.class);
143
144}