001package jmri.jmrix.bidib;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import jmri.NamedBean;
006import jmri.Turnout;
007import jmri.implementation.AbstractTurnout;
008import org.bidib.jbidibc.messages.enums.LcOutputType;
009import org.bidib.jbidibc.simulation.comm.SimulationBidib;
010
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * BiDiB implementation of the Turnout interface.
016 *
017 * @author Bob Jacobsen Copyright (C) 2001
018 * @author Eckart Meyer Copyright (C) 2019-2023
019 */
020public class BiDiBTurnout extends AbstractTurnout implements BiDiBNamedBeanInterface {
021
022    // data members
023    BiDiBAddress addr;
024    private final char typeLetter;
025
026    static String[] modeNames = null;
027    static int[] modeValues = null;
028    
029    private BiDiBTrafficController tc = null;
030    //MessageListener messageListener = null;
031    private BiDiBOutputMessageHandler messageHandler = null;
032
033    /**
034     * Create a turnout. 
035     * 
036     * @param systemName to be created
037     * @param mgr Turnout Manager, we get the memo object and the type letter (T) from the manager
038     */
039//    @SuppressWarnings("OverridableMethodCallInConstructor")
040    @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",justification = "Write safe by design")
041    public BiDiBTurnout(String systemName, BiDiBTurnoutManager mgr) {
042        super(systemName);
043        tc = mgr.getMemo().getBiDiBTrafficController();
044        addr = new BiDiBAddress(systemName, mgr.typeLetter(), mgr.getMemo());
045        log.info("New TURNOUT created: {} -> {}", systemName, addr);
046        typeLetter = mgr.typeLetter();
047        
048        // new mode list
049        if (_validFeedbackNames.length != _validFeedbackModes.length) {
050            log.error("int and string feedback arrays different length");
051        }
052        modeNames = new String[_validFeedbackNames.length + 1];
053        modeValues = new int[_validFeedbackNames.length + 1];
054        for (int i = 0; i < _validFeedbackNames.length; i++) {
055            modeNames[i] = _validFeedbackNames[i];
056            modeValues[i] = _validFeedbackModes[i];
057        }
058        modeNames[_validFeedbackNames.length] = "MONITORING";
059        modeValues[_validFeedbackNames.length] = MONITORING;
060        _validFeedbackTypes |= MONITORING;
061        _validFeedbackNames = modeNames;
062        _validFeedbackModes = modeValues;
063        
064        _activeFeedbackType = MONITORING; //default for new Turnouts
065        
066        createTurnoutListener();
067        
068        messageHandler.sendQueryConfig();
069    }
070    
071    @Override
072    public BiDiBAddress getAddr() {
073        return addr;
074    }
075    
076    /**
077     * {@inheritDoc}
078     */
079    @Override
080    public void finishLoad() {
081        sendQuery();
082    }
083    
084    /**
085     * {@inheritDoc}
086     */
087    @Override
088    public void nodeNew() {
089        //create a new BiDiBAddress
090        addr = new BiDiBAddress(getSystemName(), typeLetter, tc.getSystemConnectionMemo());
091        if (addr.isValid()) {
092            log.info("new turnout address created: {} -> {}", getSystemName(), addr);
093            messageHandler.sendQueryConfig();
094            messageHandler.waitQueryConfig();
095            log.debug("current known state is {}, commanded state is {}", getKnownState(), getCommandedState());
096//            if (getKnownState() == NamedBean.UNKNOWN  ||  getKnownState() == NamedBean.INCONSISTENT) {
097//                log.debug("state is unknown, so query from node");
098//                sendQuery();
099//            }
100//            else {
101//                log.debug("state is known, so set node");
102//                forwardCommandChangeToLayout(getKnownState());
103//            }
104            forwardCommandChangeToLayout(getCommandedState());
105        }
106    }
107
108    /**
109     * {@inheritDoc}
110     */
111    @Override
112    public void nodeLost() {
113        newKnownState(NamedBean.UNKNOWN);
114    }
115
116    /**
117     * {@inheritDoc}
118     */
119    @Override
120    public boolean canInvert() {
121        // Turnouts do support inversion
122        //log.trace("canInvert");
123        return true;
124    }
125
126    /**
127     * {@inheritDoc}
128     */
129    @Override
130    protected void forwardCommandChangeToLayout(int s) {
131        if ((tc.getBidib() instanceof SimulationBidib)  &&  addr.getAddrString().equals("Xfb7600C602:a0")) { // **** TEST hack only on for simulation
132            tc.TEST((s & Turnout.THROWN) != 0);/////DEBUG
133        }
134        // Handle a request to change state
135        // sort out states
136        log.trace("forwardCommandChangeToLayout: {}, addr: {}", s, addr);
137        if ((s & Turnout.UNKNOWN) != 0) {
138            // what to do here?
139        }
140        else {
141            if ((s & Turnout.CLOSED) != 0) {
142                // first look for the double case, which we can't handle
143                if ((s & Turnout.THROWN) != 0) {
144                    // this is the disaster case!
145                    log.error("Cannot command both CLOSED and THROWN {}", s);
146                } else {
147                    // send a CLOSED command
148                    sendMessage(true ^ getInverted());
149                }
150            } else {
151                // send a THROWN command
152                sendMessage(false ^ getInverted());
153            }
154        }
155    }
156
157    /**
158     * {@inheritDoc}
159     */
160    @Override
161    public void requestUpdateFromLayout() {
162        log.trace("requestUpdateFromLayout");
163        if (_activeFeedbackType == MONITORING) {
164            sendQuery();
165        }
166        super.requestUpdateFromLayout(); //query sensors for ONESENSOR and TWOSENSOR turnouts
167    }
168
169    @Override
170    protected void turnoutPushbuttonLockout(boolean _pushButtonLockout) {
171        // unsupported in BiDiB, but must be implemented
172    }
173    
174    /**
175     * Request the state of the accessory from the layout.
176     * The listener gets the answer.
177     */
178    public void sendQuery() {
179        messageHandler.sendQuery();
180    }
181
182    /**
183     * Send a thrown/closed message to BiDiB
184     * 
185     * @param closed true of the turnout should be switched to CLOSED, false to switch to THROWN
186     */
187    protected void sendMessage(boolean closed) {
188        // TODO: check FEATURE_GEN_SWITCH_ACK
189        int state = closed ? 0 : 1;
190        if (addr.isPortAddr()) {
191            switch (messageHandler.getLcType()) {
192                case LIGHTPORT:
193                    state = closed ? 2 : 3; //use Dim function - we can't configure this so far...
194                    break;
195                case SERVOPORT:
196                case ANALOGPORT:
197                case BACKLIGHTPORT:
198                    state = closed ? 0 : 255;
199                    break;
200                case MOTORPORT:
201                    state = closed ? 0 : 126;
202                    break;
203                case INPUTPORT:
204                    log.warn("output to INPUT port is not possible, addr: {}", addr);
205                    return;
206                default:
207                    // just drop through
208                    break;
209            }
210        }
211        if (getFeedbackMode() == MONITORING) {
212            newKnownState(INCONSISTENT);
213        }
214        messageHandler.sendOutput(state);
215    }
216        
217    
218    private void createTurnoutListener() {
219        //messageHandler = new BiDiBOutputMessageHandler("TURNOUT", addr, tc) {
220        messageHandler = new BiDiBOutputMessageHandler(this, "TURNOUT", tc) {
221            @Override
222            public void newOutputState(int state) {
223                
224                int newState = (state == 0) ? CLOSED : THROWN;
225                
226                if (addr.isPortAddr()  &&  getLcType() == LcOutputType.LIGHTPORT) {
227                    if (state == 2) {
228                        newState = CLOSED; //BIDIB_PORT_DIMM_OFF - does not make much sense though...
229                    }
230                }
231                if (getInverted()) {
232                    newState = (newState == THROWN) ? CLOSED : THROWN;
233                }
234                log.debug("TURNOUT new state: {} addr: {}", newState, addr);
235                newKnownState(newState);
236            }
237            @Override
238            public void outputWait(int time) {
239                log.debug("TURNOUT wait: {} addr: {}", time, addr);
240                //newKnownState(getCommandedState());
241            }
242            @Override
243            public void errorState(int err) {
244                log.warn("TURNOUT error: {} addr: {}", err, addr);
245                newKnownState(INCONSISTENT);
246            }
247        };
248        tc.addMessageListener(messageHandler);        
249    }
250    
251    /**
252     * {@inheritDoc}
253     * 
254     * Remove the Message Listener for this turnout
255     */
256    @Override
257    public void dispose() {
258        if (messageHandler != null) {
259            tc.removeMessageListener(messageHandler);        
260            messageHandler = null;
261        }
262        super.dispose();
263    }
264
265
266    private final static Logger log = LoggerFactory.getLogger(BiDiBTurnout.class);
267
268}