001package jmri.jmrix.tmcc;
002
003import jmri.Turnout;
004import jmri.implementation.AbstractTurnout;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Extend jmri.AbstractTurnout for TMCC serial layouts.
010 * <p>
011 * This object doesn't listen to the TMCC communications. This is because it
012 * should be the only object that is sending messages for this turnout; more
013 * than one Turnout object pointing to a single device is not allowed.
014 *
015 * @author Bob Jacobsen Copyright (C) 2003, 2006
016 * with edits/additions by
017 * @author Timothy Jump Copyright (C) 2025
018 */
019
020public class SerialTurnout extends AbstractTurnout {
021
022    // data members
023    int _number; // turnout number
024    private SerialTrafficController tc = null;
025    protected String _prefix = "T"; // default to "T"
026
027    /**
028     * Create a turnout. TMCC turnouts use the number 1-99 as their
029     * numerical identification. The TMCC SC-2 reserves 0 as a special reset
030     * address, but the TMCC SC-1 allows 0 to be a turnout; however, the SC-1
031     * documentation examples and callouts all use 1 as the first turnout
032     * address.
033     *
034     * @param prefix the connection prefix
035     * @param number the TMCC turnout number from 1 to 99
036     * @param memo   the connection memo
037     */
038    public SerialTurnout(String prefix, int number, TmccSystemConnectionMemo memo) {
039        super(prefix + "T" + number);
040        tc = memo.getTrafficController();
041        _number = number;
042        _prefix = prefix;
043        // At construction, don't register for messages (see package doc)
044    }
045
046    /**
047     * {@inheritDoc}
048     */
049    @Override
050    protected void forwardCommandChangeToLayout(int newState) {
051
052        // sort out states for TMCC (SW/Turnout)
053        if (_number < 100) {
054            if ((newState & Turnout.CLOSED) != 0) {
055                // first look for the double case, which we can't handle
056                if ((newState & Turnout.THROWN) != 0) {
057                    // this is the disaster case!
058                    log.error("Cannot command both CLOSED and THROWN {}", newState);
059                    return;
060                } else {
061                    // send a CLOSED command
062                    sendMessage(true ^ getInverted());
063                }
064            } else {
065                // send a THROWN command
066                sendMessage(false ^ getInverted());
067            }
068        }
069
070        // sort out states for TMCC (ACC/Aux1)
071        if (_number >= 100 && _number < 110) {
072            if ((newState & Turnout.CLOSED) != 0) {
073                // first look for the double case, which we can't handle
074                if ((newState & Turnout.THROWN) != 0) {
075                    // this is the disaster case!
076                    log.error("Cannot command both CLOSED and THROWN {}", newState);
077                    return;
078                } else {
079                    // send a CLOSED command
080                    sendMessage(true ^ getInverted());
081                }
082            } else {
083                // send a THROWN command
084                sendMessage(false ^ getInverted());
085            }
086        }
087
088        // sort out states for TMCC (ACC/Aux2)
089        if (_number >= 110 && _number < 120) {
090            if ((newState & Turnout.CLOSED) != 0) {
091                // first look for the double case, which we can't handle
092                if ((newState & Turnout.THROWN) != 0) {
093                    // this is the disaster case!
094                    log.error("Cannot command both CLOSED and THROWN {}", newState);
095                    return;
096                } else {
097                    // send a CLOSED command
098                    sendMessage(true ^ getInverted());
099                }
100            } else {
101                // send a THROWN command
102                sendMessage(false ^ getInverted());
103            }
104        }
105    }
106
107    @Override
108    protected void turnoutPushbuttonLockout(boolean _pushButtonLockout) {
109        if (log.isDebugEnabled()) {
110            log.debug("Send command to {} Pushbutton {}T{}", (_pushButtonLockout ? "Lock" : "Unlock"), _prefix, _number);
111        }
112    }
113
114    protected void sendMessage(boolean closed) {
115        SerialMessage m = new SerialMessage();
116        m.setOpCode(0xFE);
117
118        if (_number < 100) {
119            if (closed) {
120                m.putAsWord(0x4000 + _number * 128);
121            } else {
122                m.putAsWord(0x401F + _number * 128);
123            }
124        }
125
126        if (_number >= 100 && _number < 110) {
127            if (closed) {
128                m.putAsWord(0x8008 + ((_number - 100) * 128));
129            } else {
130                m.putAsWord(0x800B + ((_number - 100) * 128));
131            }
132        }
133
134        if (_number >= 110 && _number < 120) {
135            if (closed) {
136                m.putAsWord(0x800C + ((_number - 110) * 128));
137            } else {
138                m.putAsWord(0x800F + ((_number - 110) * 128));
139            }
140        }            
141
142        tc.sendSerialMessage(m, null);
143        tc.sendSerialMessage(m, null);
144        tc.sendSerialMessage(m, null);
145        tc.sendSerialMessage(m, null);
146    }
147
148    private final static Logger log = LoggerFactory.getLogger(SerialTurnout.class);
149
150}