001package jmri.jmrix.lenz.xnetsimulator;
002
003import java.io.DataInputStream;
004import java.io.DataOutputStream;
005import java.io.IOException;
006import java.io.PipedInputStream;
007import java.io.PipedOutputStream;
008import java.util.BitSet;
009import jmri.jmrix.ConnectionStatus;
010import jmri.jmrix.lenz.LenzCommandStation;
011import jmri.jmrix.lenz.XNetConstants;
012import jmri.jmrix.lenz.XNetInitializationManager;
013import jmri.jmrix.lenz.XNetMessage;
014import jmri.jmrix.lenz.XNetPacketizer;
015import jmri.jmrix.lenz.XNetReply;
016import jmri.jmrix.lenz.XNetSimulatorPortController;
017import jmri.jmrix.lenz.XNetTrafficController;
018import jmri.util.ImmediatePipedOutputStream;
019import org.slf4j.Logger;
020import org.slf4j.LoggerFactory;
021
022/**
023 * Provide access to a simulated XpressNet system.
024 * <p>
025 * Currently, the XNetSimulator reacts to commands sent from the user interface
026 * with messages an appropriate reply message.
027 * <p>
028 * NOTE: Most XpressNet commands are still unsupported in this implementation.
029 * <p>
030 * Normally controlled by the lenz.XNetSimulator.XNetSimulatorFrame class.
031 * <p>
032 * NOTE: Some material in this file was modified from other portions of the
033 * support infrastructure.
034 *
035 * @author Paul Bender, Copyright (C) 2009-2010
036 */
037public class XNetSimulatorAdapter extends XNetSimulatorPortController implements Runnable {
038
039    private boolean outputBufferEmpty = true;
040
041    private int csStatus;
042    // status flags from the XpressNet Documentation.
043    private static final int CS_EMERGENCY_STOP = 0x01; // bit 0
044    // 0x00 means normal mode.
045    private static final int CS_NORMAL_MODE = 0x00;
046
047    // information about the last throttle command(s).
048    private int currentSpeedStepMode = XNetConstants.LOCO_SPEED_128;
049    private int currentSpeedStep = 0;
050    private int functionGroup1 = 0;
051    private int functionGroup2 = 0;
052    private int functionGroup3 = 0;
053    private int functionGroup4 = 0;
054    private int functionGroup5 = 0;
055    //private int functionGroup6 = 0;
056    //private int functionGroup7 = 0;
057    //private int functionGroup8 = 0;
058    //private int functionGroup9 = 0;
059    //private int functionGroup10 = 0;
060
061    private int momentaryGroup1 = 0;
062    private int momentaryGroup2 = 0;
063    private int momentaryGroup3 = 0;
064    private int momentaryGroup4 = 0;
065    private int momentaryGroup5 = 0;
066    //private int momentaryGroup6 = 0;
067    //private int momentaryGroup7 = 0;
068    //private int momentaryGroup8 = 0;
069    //private int momentaryGroup9 = 0;
070    //private int momentaryGroup10 = 0;
071
072    /**
073     * Accessory state cache. A "1" bit means THROWN, "0" means
074     * CLOSED.
075     */
076    private final BitSet accessoryState = new BitSet(1024);
077
078    /**
079     * Bit is set if the accessory was operated.
080     */
081    private final BitSet accessoryOperated = new BitSet(1024);
082
083    public XNetSimulatorAdapter() {
084        setPort(Bundle.getMessage("None"));
085        try {
086            PipedOutputStream tempPipeI = new ImmediatePipedOutputStream();
087            pout = new DataOutputStream(tempPipeI);
088            inpipe = new DataInputStream(new PipedInputStream(tempPipeI));
089            PipedOutputStream tempPipeO = new ImmediatePipedOutputStream();
090            outpipe = new DataOutputStream(tempPipeO);
091            pin = new DataInputStream(new PipedInputStream(tempPipeO));
092        } catch (java.io.IOException e) {
093            log.error("init (pipe): Exception",e);
094            return;
095        }
096        csStatus = CS_NORMAL_MODE;
097    }
098
099    @Override
100    public String openPort(String portName, String appName) {
101        // open the port in XpressNet mode, check ability to set moderators
102        setPort(portName);
103        return null; // normal operation
104    }
105
106    /**
107     * Tell if the output buffer is empty or full. This should only be set to
108     * false by external processes.
109     *
110     * @param s true if the buffer is empty; false otherwise
111     */
112    @Override
113    public synchronized void setOutputBufferEmpty(boolean s) {
114        outputBufferEmpty = s;
115    }
116
117    /**
118     * Can the port accept additional characters? The state of CTS determines
119     * this, as there seems to be no way to check the number of queued bytes and
120     * buffer length. This might go false for short intervals, but it might also
121     * stick off if something goes wrong.
122     */
123    @Override
124    public boolean okToSend() {
125        boolean checkBuffer = true;
126        if (checkBuffer) {
127            log.debug("Buffer Empty: {}", outputBufferEmpty);
128            return (outputBufferEmpty && super.okToSend());
129        } else {
130            log.debug("No Flow Control or Buffer Check");
131            return (super.okToSend());
132        }
133    }
134
135    /**
136     * Set up all of the other objects to operate with an XNetSimulator connected
137     * to this port.
138     */
139    @Override
140    public void configure() {
141        // connect to a packetizing traffic controller
142        XNetTrafficController packets = new XNetPacketizer(new LenzCommandStation());
143        configure(packets);
144    }
145
146    protected void configure(XNetTrafficController packets) {
147        packets.connectPort(this);
148
149        // start operation
150        this.getSystemConnectionMemo().setXNetTrafficController(packets);
151
152        sourceThread = new Thread(this);
153        sourceThread.start();
154
155        new XNetInitializationManager()
156                .memo(this.getSystemConnectionMemo())
157                .setDefaults()
158                .versionCheck()
159                .setTimeout(30000)
160                .init();
161    }
162
163    // Base class methods for the XNetSimulatorPortController interface
164
165    @Override
166    public DataInputStream getInputStream() {
167        if (pin == null) {
168            log.error("getInputStream called before load(), stream not available");
169            ConnectionStatus.instance().setConnectionState(
170                    this.getSystemConnectionMemo().getUserName(),
171                    this.getCurrentPortName(), ConnectionStatus.CONNECTION_DOWN);
172        }
173        return pin;
174    }
175
176    @Override
177    public DataOutputStream getOutputStream() {
178        if (pout == null) {
179            log.error("getOutputStream called before load(), stream not available");
180            ConnectionStatus.instance().setConnectionState(
181                    this.getSystemConnectionMemo().getUserName(),
182                    this.getCurrentPortName(), ConnectionStatus.CONNECTION_DOWN);
183        }
184        return pout;
185    }
186
187    @Override
188    public boolean status() {
189        return (pout != null && pin != null);
190    }
191
192    @Override
193    public void run() { // start a new thread
194        // this thread has one task.  It repeatedly reads from the input pipe
195        // and writes modified data to the output pipe.  This is the heart
196        // of the command station simulation.
197        log.debug("Simulator Thread Started");
198        ConnectionStatus.instance().setConnectionState(
199                this.getSystemConnectionMemo().getUserName(),
200                this.getCurrentPortName(), ConnectionStatus.CONNECTION_UP);
201        for (;;) {
202            XNetMessage m = readMessage();
203            log.debug("Simulator Thread received message {}", m);
204            XNetReply r = generateReply(m);
205            writeReply(r);
206            log.debug("Simulator Thread sent Reply {}", r);
207        }
208    }
209
210    // Read one incoming message from the buffer
211    // and set outputBufferEmpty to true.
212    private XNetMessage readMessage() {
213        XNetMessage msg = null;
214        try {
215            msg = loadChars();
216        } catch (java.io.IOException e) {
217            // should do something meaningful here.
218            ConnectionStatus.instance().setConnectionState(
219                    this.getSystemConnectionMemo().getUserName(),
220                    this.getCurrentPortName(), ConnectionStatus.CONNECTION_DOWN);
221
222        }
223        setOutputBufferEmpty(true);
224        return (msg);
225    }
226
227    // This is the heart of the simulation. It translates an
228    // incoming XNetMessage into an outgoing XNetReply.
229    private XNetReply generateReply(XNetMessage m) {
230        XNetReply reply = new XNetReply();
231        switch (m.getElement(0) & 0xff) {
232
233            case XNetConstants.CS_REQUEST:
234                switch (m.getElement(1) & 0xff ) {
235                    case XNetConstants.CS_VERSION:
236                        reply = xNetVersionReply();
237                        break;
238                    case XNetConstants.RESUME_OPS:
239                        csStatus = CS_NORMAL_MODE;
240                        reply = normalOpsReply();
241                        break;
242                    case XNetConstants.EMERGENCY_OFF:
243                        csStatus = CS_EMERGENCY_STOP;
244                        reply = everythingOffReply();
245                        break;
246                    case XNetConstants.CS_STATUS:
247                        reply = csStatusReply();
248                        break;
249                    case XNetConstants.SERVICE_MODE_CSRESULT:
250                    default:
251                        reply = notSupportedReply();
252                }
253                break;
254            case XNetConstants.LI_VERSION_REQUEST:
255                reply.setOpCode(XNetConstants.LI_VERSION_RESPONSE);
256                reply.setElement(1, 0x00);  // set the hardware type to 0
257                reply.setElement(2, 0x00);  // set the firmware version to 0
258                reply.setElement(3, 0x00);  // set the parity byte to 0
259                reply.setParity();
260                break;
261            case XNetConstants.LOCO_OPER_REQ:
262                switch (m.getElement(1) & 0xff ) {
263                    case XNetConstants.LOCO_SPEED_14:
264                        currentSpeedStepMode = XNetConstants.LOCO_SPEED_14;
265                        currentSpeedStep = m.getElement(4);
266                        reply = okReply();
267                        break;
268                    case XNetConstants.LOCO_SPEED_27:
269                        currentSpeedStepMode = XNetConstants.LOCO_SPEED_27;
270                        currentSpeedStep = m.getElement(4);
271                        reply = okReply();
272                        break;
273                    case XNetConstants.LOCO_SPEED_28:
274                        currentSpeedStepMode = XNetConstants.LOCO_SPEED_28;
275                        currentSpeedStep = m.getElement(4);
276                        reply = okReply();
277                        break;
278                    case XNetConstants.LOCO_SPEED_128:
279                        currentSpeedStepMode = XNetConstants.LOCO_SPEED_128;
280                        currentSpeedStep = m.getElement(4);
281                        reply = okReply();
282                        break;
283
284                    case XNetConstants.LOCO_SET_FUNC_GROUP1:
285                        functionGroup1 = m.getElement(4);
286                        reply = okReply();
287                        break;
288                    case XNetConstants.LOCO_SET_FUNC_GROUP2:
289                        functionGroup2 = m.getElement(4);
290                        reply = okReply();
291                        break;
292                    case XNetConstants.LOCO_SET_FUNC_GROUP3:
293                        functionGroup3 = m.getElement(4);
294                        reply = okReply();
295                        break;
296                    case XNetConstants.LOCO_SET_FUNC_GROUP4:
297                        functionGroup4 = m.getElement(4);
298                        reply = okReply();
299                        break;
300                    case XNetConstants.LOCO_SET_FUNC_GROUP5:
301                        functionGroup5 = m.getElement(4);
302                        reply = okReply();
303                        break;
304
305                    case XNetConstants.LOCO_SET_FUNC_GROUP1_MOMENTARY:
306                        momentaryGroup1 = m.getElement(4);
307                        reply = okReply();
308                        break;
309                    case XNetConstants.LOCO_SET_FUNC_GROUP2_MOMENTARY:
310                        momentaryGroup2 = m.getElement(4);
311                        reply = okReply();
312                        break;
313                    case XNetConstants.LOCO_SET_FUNC_GROUP3_MOMENTARY:
314                        momentaryGroup3 = m.getElement(4);
315                        reply = okReply();
316                        break;
317                    case XNetConstants.LOCO_SET_FUNC_GROUP4_MOMENTARY:
318                        momentaryGroup4 = m.getElement(4);
319                        reply = okReply();
320                        break;
321                    case XNetConstants.LOCO_SET_FUNC_GROUP5_MOMENTARY:
322                        momentaryGroup5 = m.getElement(4);
323                        reply = okReply();
324                        break;
325
326
327                    case XNetConstants.LOCO_SET_FUNC_GROUP6:
328                        //functionGroup6 = m.getElement(4);
329                        //reply = okReply();
330                        //break;
331                    case XNetConstants.LOCO_SET_FUNC_GROUP7:
332                        //functionGroup7 = m.getElement(4);
333                        //reply = okReply();
334                        //break;
335                    case XNetConstants.LOCO_SET_FUNC_GROUP8:
336                        //functionGroup8 = m.getElement(4);
337                        //reply = okReply();
338                        //break;
339                    case XNetConstants.LOCO_SET_FUNC_GROUP9:
340                        //functionGroup9 = m.getElement(4);
341                        //reply = okReply();
342                        //break;
343                    case XNetConstants.LOCO_SET_FUNC_GROUP10:
344                        //functionGroup10 = m.getElement(4);
345                        //reply = okReply();
346                        //break;
347                    case XNetConstants.LOCO_SET_FUNC_GROUP6_MOMENTARY:
348                        //momentaryGroup6 = m.getElement(4);
349                        //reply = okReply();
350                        //break;
351                    case XNetConstants.LOCO_SET_FUNC_GROUP7_MOMENTARY:
352                        //momentaryGroup7 = m.getElement(4);
353                        //reply = okReply();
354                        //break;
355                    case XNetConstants.LOCO_SET_FUNC_GROUP8_MOMENTARY:
356                        //momentaryGroup8 = m.getElement(4);
357                        //reply = okReply();
358                        //break;
359                    case XNetConstants.LOCO_SET_FUNC_GROUP9_MOMENTARY:
360                        //momentaryGroup9 = m.getElement(4);
361                        //reply = okReply();
362                        //break;
363                    case XNetConstants.LOCO_SET_FUNC_GROUP10_MOMENTARY:
364                        //momentaryGroup10 = m.getElement(4);
365                        reply = okReply();
366                        break;
367
368                    case XNetConstants.LOCO_ADD_MULTI_UNIT_REQ:
369                    case XNetConstants.LOCO_REM_MULTI_UNIT_REQ:
370                    case XNetConstants.LOCO_IN_MULTI_UNIT_REQ_FORWARD:
371                    case XNetConstants.LOCO_IN_MULTI_UNIT_REQ_BACKWARD:
372                    default:
373                        reply = notSupportedReply();
374                        break;
375                }
376                break;
377            case XNetConstants.ALL_ESTOP:    // ALL_ESTOP is XNet V4
378                csStatus = CS_EMERGENCY_STOP;
379                reply = emergencyStopReply();
380                break;
381            case XNetConstants.EMERGENCY_STOP:
382            case XNetConstants.EMERGENCY_STOP_XNETV1V2:
383                reply = okReply();
384                break;
385            case XNetConstants.ACC_OPER_REQ:
386                // LZ100 and LZV100 respond with an ACC_INFO_RESPONSE.
387                // but XpressNet standard says to no response (which causes
388                // the interface to send an OK reply).
389                reply = accReqReply(m);
390                break;
391            case XNetConstants.ACC_INFO_REQ:
392                reply = accInfoReply(m);
393                break;
394            case XNetConstants.LOCO_STATUS_REQ:
395                switch (m.getElement(1) & 0xff ) {
396                    case XNetConstants.LOCO_INFO_REQ_V3:
397                        reply.setOpCode(XNetConstants.LOCO_INFO_NORMAL_UNIT);
398                        reply.setElement(1, currentSpeedStepMode);
399                        reply.setElement(2, currentSpeedStep);  // set the speed
400                        // direction reverse
401                        reply.setElement(3, functionGroup1);  // set function group 1
402                        reply.setElement(4, (functionGroup2 & 0x0f) + ((functionGroup3 & 0x0f) << 4));  // set function group 2 and 3
403                        reply.setElement(5, 0x00);  // set the parity byte to 0
404                        reply.setParity();         // set the parity correctly.
405                        break;
406                    case XNetConstants.LOCO_INFO_REQ_FUNC:
407                        reply.setOpCode(XNetConstants.LOCO_INFO_RESPONSE);
408                        reply.setElement(1, XNetConstants.LOCO_FUNCTION_STATUS);  // momentary function status
409                        reply.setElement(2, momentaryGroup1);  // set function group 1
410                        reply.setElement(3, (momentaryGroup2 & 0x0f) + ((momentaryGroup3 * 0x0f) << 4));  // set function group 2 and 3
411                        reply.setElement(4, 0x00);  // set the parity byte to 0
412                        reply.setParity();         // set the parity correctly.
413                        break;
414                    case XNetConstants.LOCO_INFO_REQ_FUNC_HI_ON:
415                        reply.setOpCode(XNetConstants.LOCO_INFO_RESPONSE);
416                        reply.setElement(1, XNetConstants.LOCO_FUNCTION_STATUS_HIGH);  // F13-F28 function on/off status
417                        reply.setElement(2, functionGroup4);  // set function group 4
418                        reply.setElement(3, functionGroup5);  // set function group 5
419                        reply.setElement(4, 0x00);  // set the parity byte to 0
420                        reply.setParity();         // set the parity correctly.
421                        break;
422                    case XNetConstants.LOCO_INFO_REQ_FUNC_HI_MOM:
423                        reply.setOpCode(XNetConstants.LOCO_INFO_NORMAL_UNIT);
424                        reply.setElement(1, XNetConstants.LOCO_FUNCTION_STATUS_HIGH_MOM);  // F13-F28 momentary function status
425                        reply.setElement(2, momentaryGroup4);  // set function group 4
426                        reply.setElement(3, momentaryGroup5);  // set function group 5
427                        reply.setElement(4, 0x00);  // set the parity byte to 0
428                        reply.setParity();         // set the parity correctly.
429                        break;
430                    default:
431                        reply = notSupportedReply();
432                }
433                break;
434            case XNetConstants.OPS_MODE_PROG_REQ:
435                    int operation = m.getElement(4) & 0xFC;
436                    switch(operation & 0xff ) {
437                         case 0xEC:
438                           log.debug("Write CV in Ops Mode Request Received");
439                           reply = okReply();
440                           break;
441                         case 0xE4:
442                           log.debug("Verify CV in Ops Mode Request Received");
443                           reply = okReply();
444                           break;
445                         case 0xE8:
446                           log.debug("Ops Mode Bit Request Received");
447                           reply = okReply();
448                           break;
449                         default:
450                           reply=notSupportedReply();
451                    }
452                break;
453            case XNetConstants.LI101_REQUEST:
454            case XNetConstants.CS_SET_POWERMODE:
455            //case XNetConstants.PROG_READ_REQUEST:  //PROG_READ_REQUEST
456            //and CS_SET_POWERMODE
457            //have the same value
458            case XNetConstants.PROG_WRITE_REQUEST:
459            case XNetConstants.LOCO_DOUBLEHEAD:
460            default:
461                reply = notSupportedReply();
462        }
463        return (reply);
464    }
465
466    // We have a few canned response messages.
467    // Create an Unsupported XNetReply message
468    private XNetReply notSupportedReply() {
469        XNetReply r = new XNetReply();
470        r.setOpCode(XNetConstants.CS_INFO);
471        r.setElement(1, XNetConstants.CS_NOT_SUPPORTED);
472        r.setElement(2, 0x00); // set the parity byte to 0
473        r.setParity();
474        return r;
475    }
476
477    // Create an OK XNetReply message
478    private XNetReply okReply() {
479        XNetReply r = new XNetReply();
480        r.setOpCode(XNetConstants.LI_MESSAGE_RESPONSE_HEADER);
481        r.setElement(1, XNetConstants.LI_MESSAGE_RESPONSE_SEND_SUCCESS);
482        r.setElement(2, 0x00); // set the parity byte to 0
483        r.setParity();
484        return r;
485    }
486
487    // Create a "Normal Operations Resumed" message
488    private XNetReply normalOpsReply() {
489        XNetReply r = new XNetReply();
490        r.setOpCode(XNetConstants.CS_INFO);
491        r.setElement(1, XNetConstants.BC_NORMAL_OPERATIONS);
492        r.setElement(2, 0x00); // set the parity byte to 0
493        r.setParity();
494        return r;
495    }
496
497    // Create a broadcast "Everything Off" reply
498    private XNetReply everythingOffReply() {
499        XNetReply r = new XNetReply();
500        r.setOpCode(XNetConstants.CS_INFO);
501        r.setElement(1, XNetConstants.BC_EVERYTHING_OFF);
502        r.setElement(2, 0x00); // set the parity byte to 0
503        r.setParity();
504        return r;
505    }
506
507    // Create a broadcast "Emergency Stop" reply
508    private XNetReply emergencyStopReply() {
509        XNetReply r = new XNetReply();
510        r.setOpCode(XNetConstants.BC_EMERGENCY_STOP);
511        r.setElement(1, XNetConstants.BC_EVERYTHING_STOP);
512        r.setElement(2, 0x00); // set the parity byte to 0
513        r.setParity();
514        return r;
515    }
516
517    // Create a reply to a request for the XpressNet Version
518    private XNetReply xNetVersionReply() {
519        XNetReply reply = new XNetReply();
520        reply.setOpCode(XNetConstants.CS_SERVICE_MODE_RESPONSE);
521        reply.setElement(1, XNetConstants.CS_SOFTWARE_VERSION);
522        reply.setElement(2, 0x40); // indicate we are version 4.0
523        reply.setElement(3, 0x00 ); // indicate we are an LZ100
524        reply.setElement(4, 0x00); // set the parity byte to 0
525        reply.setParity();
526        return reply;
527    }
528
529    // Create a reply to a request for the Command Station Status
530    private XNetReply csStatusReply() {
531        XNetReply reply = new XNetReply();
532        reply.setOpCode(XNetConstants.CS_REQUEST_RESPONSE);
533        reply.setElement(1, XNetConstants.CS_STATUS_RESPONSE);
534        reply.setElement(2, csStatus);
535        reply.setElement(3, 0x00); // set the parity byte to 0
536        reply.setParity();
537        return reply;
538    }
539
540    /**
541     * Return the turnout feedback type.
542     * <ul>
543     * <li>0x00 - turnout without feedback, ie DR5000
544     * <li>0x01 - turnout with feedback, ie NanoX
545     * <li>0x10 - feedback module
546     * </ul>
547     * @return the turnout type reported by this station.
548     */
549    protected int getTurnoutFeedbackType() {
550        return 0x01;
551    }
552
553    /**
554     * Returns accessory state, in the Operation Info Reply bit format. If the
555     * accessory has not been operated yet, returns 00 (not operated).
556     *
557     * @param a accessory number
558     * @return two bits representing the accessory state.
559     */
560    protected int getAccessoryStateBits(int a) {
561        if (!accessoryOperated.get(a)) {
562            return 0x00;
563        }
564        boolean state = accessoryState.get(a);
565        int zbits = state ? 0b10 : 0b01;
566        return zbits;
567    }
568
569    protected XNetReply accInfoReply(XNetMessage m) {
570        if (m.getElement(1) >= 64) {
571            return feedbackInfoReply(m);
572        } else {
573            boolean nibble = (m.getElement(2) & 0x01) == 0x01;
574            int ba = m.getElement(1);
575            return accInfoReply(ba, nibble);
576        }
577    }
578
579    protected XNetReply feedbackInfoReply(XNetMessage m) {
580        XNetReply reply = new XNetReply();
581       reply.setOpCode(XNetConstants.ACC_INFO_RESPONSE);
582        reply.setElement(1, m.getElement(1));
583        // treat as feedback encoder request.
584        if (m.getElement(2) == 0x80) {
585            reply.setElement(2, 0x40);
586        } else {
587            reply.setElement(2, 0x50);
588        }
589       reply.setElement(3, 0x00);
590       reply.setParity();
591       return reply;
592    }
593
594    /**
595     * Creates a reply packet for a turnout/accessory.
596     * @param baseAddress base address for the feedback, the 4-turnout block; numbered from 0
597     * @param nibble lower or upper nibble (2 turnout block) delivered in the reply
598     * @return constructed reply.
599     */
600    protected XNetReply accInfoReply(int baseAddress, boolean nibble) {
601        XNetReply r = new XNetReply();
602        r.setOpCode(XNetConstants.ACC_INFO_RESPONSE);
603        r.setElement(1, baseAddress);
604        int nibbleVal = 0;
605        int a = baseAddress * 4 + 1;
606        if (nibble) {
607            a += 2;
608        }
609        int zbits = getAccessoryStateBits(a++);
610        nibbleVal |= zbits;
611        zbits = getAccessoryStateBits(a++);
612        nibbleVal |= (zbits << 2);
613        r.setElement(2, // 0 << 7 |          // turnout movement completed, unsupported; always done
614            getTurnoutFeedbackType() << 5 | // two bits: accessory without feedback
615            (nibble ? 1 : 0) << 4 |         // upper / lower nibble
616            nibbleVal & 0x0f);
617        r.setElement(3, 0);
618        r.setParity();
619        return r;
620    }
621
622    /**
623     * Generate reply to accessory request command.
624     * The returned XNetReply is the first to be returned by this simulated command station.
625     * @param address the accessory address
626     * @param output the output to be manipulated
627     * @param state true if output should be on, false for off
628     * @param previousAccessoryState the previous accessory state
629     * @return the reply instance.
630     */
631    protected XNetReply generateAccRequestReply(int address, int output, boolean state, boolean previousAccessoryState) {
632        XNetReply r;
633
634        if (state) {
635            if (accessoryOperated.get(address) && previousAccessoryState == (output != 0)) {
636                // just OK, the accessory is in the same state.
637                return okReply();
638            } else {
639                accessoryOperated.set(address);
640                r = accInfoReply(address);
641                r.setUnsolicited();
642            }
643        } else {
644            accessoryOperated.set(address);
645            // generate just OK to OFF
646            r = okReply();
647        }
648        return r;
649    }
650
651    /**
652     * Creates a reply for the specific turnout dcc address.
653     * @param dccTurnoutAddress the turnout address
654     * @return a reply packet
655     */
656    protected XNetReply accInfoReply(int dccTurnoutAddress) {
657        dccTurnoutAddress--;
658        int baseAddress = dccTurnoutAddress / 4;
659        boolean upperNibble = dccTurnoutAddress % 4 >= 2;
660        return accInfoReply(baseAddress, upperNibble);
661    }
662
663    protected XNetReply accReqReply(XNetMessage m) {
664        int baseaddress = m.getElement(1);
665        int subaddress = (m.getElement(2) & 0x06) >> 1;
666        int address = (baseaddress * 4) + subaddress + 1;
667        int output = m.getElement(2) & 0x01;
668        boolean on = ((m.getElement(2) & 0x08)) == 0x08;
669        boolean oldState = accessoryState.get(address);
670        if (on) {
671            accessoryState.set(address, output != 0);
672        }
673        log.debug("Received command {} ... {}", m, m.toMonitorString());
674        return generateAccRequestReply(address, output, on, oldState);
675    }
676
677    private void writeReply(XNetReply r) {
678        int i;
679        int len = (r.getElement(0) & 0x0f) + 2;  // opCode+Nbytes+ECC
680        for (i = 0; i < len; i++) {
681            try {
682                outpipe.writeByte((byte) r.getElement(i));
683            } catch (java.io.IOException ex) {
684                ConnectionStatus.instance().setConnectionState(
685                        this.getSystemConnectionMemo().getUserName(),
686                        this.getCurrentPortName(), ConnectionStatus.CONNECTION_DOWN);
687            }
688        }
689    }
690
691    /**
692     * Get characters from the input source, and file a message.
693     * <p>
694     * Returns only when the message is complete.
695     * <p>
696     * Only used in the Receive thread.
697     *
698     * @return filled message
699     * @throws IOException when presented by the input source.
700     */
701    protected XNetMessage loadChars() throws java.io.IOException {
702        int i;
703        byte char1;
704        char1 = readByteProtected(inpipe);
705        int len = (char1 & 0x0f) + 2;  // opCode+Nbytes+ECC
706        XNetMessage msg = new XNetMessage(len);
707        msg.setElement(0, char1 & 0xFF);
708        for (i = 1; i < len; i++) {
709            char1 = readByteProtected(inpipe);
710            msg.setElement(i, char1 & 0xFF);
711        }
712        return msg;
713    }
714
715    /**
716     * Read a single byte, protecting against various timeouts, etc.
717     * <p>
718     * When a port is set to have a receive timeout (via the
719     * enableReceiveTimeout() method), some will return zero bytes or an
720     * EOFException at the end of the timeout. In that case, the read should be
721     * repeated to get the next real character.
722     * @param istream the input data source
723     * @return the next byte, waiting for it to become available
724     * @throws java.io.IOException from the underlying operations
725     */
726    protected byte readByteProtected(DataInputStream istream) throws java.io.IOException {
727        byte[] rcvBuffer = new byte[1];
728        while (true) { // loop will repeat until character found
729            int nchars;
730            nchars = istream.read(rcvBuffer, 0, 1);
731            if (nchars > 0) {
732                return rcvBuffer[0];
733            }
734        }
735    }
736
737    private DataOutputStream pout = null; // for output to other classes
738    private DataInputStream pin = null; // for input from other classes
739    // internal ends of the pipes
740    private DataOutputStream outpipe = null;  // feed pin
741    private DataInputStream inpipe = null; // feed pout
742    private Thread sourceThread;
743
744    private static final Logger log = LoggerFactory.getLogger(XNetSimulatorAdapter.class);
745
746}