001package jmri.jmrix.loconet.locobuffer;
002
003import java.util.Arrays;
004import java.util.Vector;
005import jmri.jmrix.loconet.LnCommandStationType;
006import jmri.jmrix.loconet.LnPacketizer;
007import jmri.jmrix.loconet.LnPacketizerStrict;
008import jmri.jmrix.loconet.LnPortController;
009import jmri.jmrix.loconet.LocoNetSystemConnectionMemo;
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013/**
014 * Provide access to LocoNet via a LocoBuffer attached to a serial com port.
015 * <p>
016 * Normally controlled by the LocoBufferFrame class.
017 *
018 * @author Bob Jacobsen Copyright (C) 2001, 2008, 2010
019 */
020public class LocoBufferAdapter extends LnPortController {
021
022    public LocoBufferAdapter() {
023        this(new LocoNetSystemConnectionMemo());
024    }
025
026    public LocoBufferAdapter(LocoNetSystemConnectionMemo adapterMemo) {
027        super(adapterMemo);
028        option1Name = "FlowControl"; // NOI18N
029        option2Name = "CommandStation"; // NOI18N
030        option3Name = "TurnoutHandle"; // NOI18N
031        option4Name = "PacketizerType"; // NOI18N
032        options.put(option1Name, new Option(Bundle.getMessage("XconnectionUsesLabel", Bundle.getMessage("TypeSerial")), validOption1));  // NOI18N
033        options.put(option2Name, new Option(Bundle.getMessage("CommandStationTypeLabel"), getCommandStationListWithStandaloneLN(), false));  // NOI18N
034        options.put(option3Name, new Option(Bundle.getMessage("TurnoutHandling"),
035                new String[]{Bundle.getMessage("HandleNormal"), Bundle.getMessage("HandleSpread"), Bundle.getMessage("HandleOneOnly"), Bundle.getMessage("HandleBoth")})); // I18N
036        options.put(option4Name, new Option(Bundle.getMessage("PacketizerTypeLabel"), packetizerOptions()));  // NOI18N
037        options.put("TranspondingPresent", new Option(Bundle.getMessage("TranspondingPresent"),
038                new String[]{Bundle.getMessage("ButtonNo"), Bundle.getMessage("ButtonYes")} )); // NOI18N
039        options.put("InterrogateOnStart", new Option(Bundle.getMessage("InterrogateOnStart"),
040                new String[]{Bundle.getMessage("ButtonYes"), Bundle.getMessage("ButtonNo")} )); // NOI18N
041        options.put("LoconetProtocolAutoDetect", new Option(Bundle.getMessage("LoconetProtocolAutoDetectLabel"),
042                new String[]{Bundle.getMessage("ButtonNo"),Bundle.getMessage("LoconetProtocolAutoDetect")} )); // NOI18N
043        options.put("LoconetUpdateSlotOnMessageCreation",                                       // NOI18N
044                new Option(Bundle.getMessage("LoconetUpdateSlotOnMessageCreationLabel"),        // I18N
045                new String[]{Bundle.getMessage("ButtonNo"),Bundle.getMessage("ButtonYes")} ));  // I18N
046    }
047    
048    /**
049     * Create a list of possible command stations and append "Standalone LocoNet"
050     * 
051     * Note: This is not suitable for use by any class which extends this class if
052     * the hardware interface is part of a command station.
053     * 
054     * @return String[] containing the array of command stations, plus "Standalone 
055     *          LocoNet"
056     */
057    public String[] getCommandStationListWithStandaloneLN() {
058        String[] result = new String[commandStationNames.length + 1];
059        for (int i = 0 ; i < result.length-1; ++i) {
060            result[i] = commandStationNames[i];
061        }
062        result[commandStationNames.length] = LnCommandStationType.COMMAND_STATION_STANDALONE.getName();
063        return result;
064    }
065    
066    Vector<String> portNameVector = null;
067
068    @Override
069    public String openPort(String portName, String appName) {
070        // get and open the primary port
071        currentSerialPort = activatePort(portName, log);
072        if (currentSerialPort == null) {
073            log.error("failed to connect LocoBuffer to {}", portName);
074            return Bundle.getMessage("SerialPortNotFound", portName);
075        }
076        reportOpen(portName);
077        
078        // try to set it for communication via SerialDriver
079        // find the baud rate value, configure comm options
080        int baud = currentBaudNumber(mBaudRate);
081        setBaudRate(currentSerialPort, baud);
082        configureLeads(currentSerialPort, true, true);
083        setLocalFlowControl();
084
085        setComPortTimeouts(currentSerialPort, Blocking.READ_SEMI_BLOCKING, 100);
086
087        // report status
088        reportPortStatus(log, portName);
089
090        opened = true;
091
092        return null; // indicates OK return
093    }
094
095    /**
096     * Allow subtypes to change the opening message
097     * @param portName To appear in message
098     */
099    protected void reportOpen(String portName) {
100        log.info("Connecting LocoBuffer via {} {}", portName, currentSerialPort);
101    }
102    
103    /**
104     * Allow subtypes to change the flow control algorithm
105     */
106    protected void setLocalFlowControl() {
107        FlowControl flow = FlowControl.RTSCTS;
108        if (getOptionState(option1Name).equals(validOption1[1])) {
109            flow = FlowControl.NONE;
110        }
111        setFlowControl(currentSerialPort, flow);
112    }
113    
114    /**
115     * Can the port accept additional characters? The state of CTS determines
116     * this, as there seems to be no way to check the number of queued bytes and
117     * buffer length. This might go false for short intervals, but it might also
118     * stick off if something goes wrong.
119     * 
120     * @return an indication of whether the interface is accepting transmit messages.
121     */
122    @Override
123    public boolean okToSend() {
124        return currentSerialPort.getCTS();
125    }
126
127    /**
128     * Set up all of the other objects to operate with a LocoBuffer connected to
129     * this port.
130     */
131    @Override
132    public void configure() {
133
134        setCommandStationType(getOptionState(option2Name));
135        setTurnoutHandling(getOptionState(option3Name));
136        setTranspondingAvailable(getOptionState("TranspondingPresent"));
137        setInterrogateOnStart(getOptionState("InterrogateOnStart"));
138        setLoconetProtocolAutoDetect(getOptionState("LoconetProtocolAutoDetect"));
139        // connect to a packetizing traffic controller
140        LnPacketizer packets = getPacketizer(getOptionState(option4Name));
141        packets.setLoconetUpdateSlotOnMessageCreation(Bundle.getMessage("ButtonYes").equals(getOptionState("LoconetUpdateSlotOnMessageCreation")));
142        packets.connectPort(this);
143
144        // create memo
145        this.getSystemConnectionMemo().setLnTrafficController(packets);
146        // do the common manager config
147
148        this.getSystemConnectionMemo().configureCommandStation(commandStationType,
149                mTurnoutNoRetry, mTurnoutExtraSpace, mTranspondingAvailable, mInterrogateAtStart, mLoconetProtocolAutoDetect);
150        this.getSystemConnectionMemo().configureManagers();
151
152        // start operation
153        packets.startThreads();
154    }
155
156    @Override
157    public boolean status() {
158        return opened;
159    }
160
161    /**
162     * {@inheritDoc}
163     */
164    @Override
165    public String[] validBaudRates() {
166        return Arrays.copyOf(validSpeeds, validSpeeds.length);
167    }
168
169    /**
170     * {@inheritDoc}
171     */
172    @Override
173    public int[] validBaudNumbers() {
174        return Arrays.copyOf(validSpeedValues, validSpeedValues.length);
175    }
176
177    protected String[] validSpeeds = new String[]{Bundle.getMessage("Baud19200LB"), Bundle.getMessage("Baud57600LB")};
178    protected int[] validSpeedValues = new int[]{19200, 57600};
179
180    @Override
181    public int defaultBaudIndex() {
182        return 0;
183    }
184
185    // meanings are assigned to these above, so make sure the order is consistent
186    protected String[] validOption1 = new String[]{Bundle.getMessage("FlowOptionHwRecomm"), Bundle.getMessage("FlowOptionNo")};
187
188    /**
189     *  Define the readable data and internal code
190     */
191    private static String[][] packetizers = { {Bundle.getMessage("PacketizerTypelnPacketizer"),"lnPacketizer" },
192            {Bundle.getMessage("PacketizerTypelnPacketizerStrict"),"lnPacketizerStrict"} };
193
194    /**
195     *
196     * @return String array of readable choices
197     */
198    private String[] packetizerOptions() {
199        String[] retval = new String[packetizers.length];
200        for (int i=0;i < packetizers.length; i++) {
201            retval[i] = packetizers[i][0];
202        }
203        return retval;
204    }
205    /**
206     * for a given readable choice return internal value
207     * or the default
208     *
209     * @param s  string containing ?a packetizer name?
210     * @return internal value
211     */
212    protected String getPacketizerOption(String s) {
213        for (int i=0;i < packetizers.length; i++) {
214            if (packetizers[i][0].equals(s)) {
215                return packetizers[i][1];
216            }
217        }
218        return "lnPacketizer";
219    }
220    /**
221     * 
222     * @param s the packetizer to use in its readable form.
223     * @return a LnPacketizer
224     */
225    protected LnPacketizer getPacketizer(String s) {
226        LnPacketizer packets;
227        String packetSelection = getPacketizerOption(s);
228        switch (packetSelection) {
229            case "lnPacketizer":
230                packets = new LnPacketizer(this.getSystemConnectionMemo());
231                break;
232            case "lnPacketizerStrict":
233                packets = new LnPacketizerStrict(this.getSystemConnectionMemo());
234                break;
235            default:
236                packets = new LnPacketizer(this.getSystemConnectionMemo());
237                log.warn("Using Normal do not understand option [{}]", packetSelection);
238        }
239        return packets;
240    }
241
242    private static final Logger log = LoggerFactory.getLogger(LocoBufferAdapter.class);
243
244}