001package jmri.jmrix.can.cbus;
002
003import jmri.Turnout;
004import jmri.jmrix.can.CanListener;
005import jmri.jmrix.can.CanMessage;
006import jmri.jmrix.can.CanReply;
007import jmri.jmrix.can.TrafficController;
008
009/**
010 * Turnout for CBUS connections.
011 *
012 * @author Bob Jacobsen Copyright (C) 2001
013 */
014public class CbusTurnout extends jmri.implementation.AbstractTurnout
015        implements CanListener, CbusEventInterface {
016
017    private CbusAddress addrThrown;   // go to thrown state
018    private CbusAddress addrClosed;   // go to closed state
019
020    protected CbusTurnout(String prefix, String address, TrafficController tc) {
021        super(prefix + "T" + address);
022        this.tc = tc;
023        init(address);
024
025    }
026
027    private final TrafficController tc;
028
029    /**
030     * Common initialization for both constructors.
031     */
032    private void init(String address) {
033        // build local addresses
034        CbusAddress a = new CbusAddress(address);
035        CbusAddress[] v = a.split();
036        switch (v.length) {
037            case 1:
038                addrThrown = v[0];
039                // need to complement here for addr 1
040                // so address _must_ start with address + or -
041                if (address.startsWith("+")) {
042                    addrClosed = new CbusAddress("-" + address.substring(1));
043                } else if (address.startsWith("-")) {
044                    addrClosed = new CbusAddress("+" + address.substring(1));
045                } else {
046                    log.error("can't make 2nd event from systemname {}", address);
047                    return;
048                }
049                break;
050            case 2:
051                addrThrown = v[0];
052                addrClosed = v[1];
053                break;
054            default:
055                log.error("Can't parse CbusTurnout system name: {}", address);
056                return;
057        }
058        // connect
059        addTc(tc);
060    }
061
062    /**
063     * Request an update on status by sending CBUS request message to thrown address.
064     */
065    @Override
066    public void requestUpdateFromLayout() {
067        CanMessage m = addrThrown.makeMessage(tc.getCanid());
068        if (CbusOpCodes.isShortEvent(CbusMessage.getOpcode(m))) {
069            m.setOpCode(CbusConstants.CBUS_ASRQ);
070        }
071        else {
072            m.setOpCode(CbusConstants.CBUS_AREQ);
073        }
074        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
075        tc.sendCanMessage(m, this);
076
077        super.requestUpdateFromLayout(); // request update from feedback sensors.
078    }
079
080    /**
081     * {@inheritDoc}
082     * Sends a CBUS event.
083     */
084    @Override
085    protected void forwardCommandChangeToLayout(int newState) {
086        CanMessage m;
087        if (newState == Turnout.THROWN) {
088            m = getAddrThrown();
089            CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
090            tc.sendCanMessage(m, this);
091        } 
092        if (newState == Turnout.CLOSED) {
093            m = getAddrClosed();
094            CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
095            tc.sendCanMessage(m, this);
096        }
097    }
098    
099    /**
100     * Package method returning CanMessage for the Thrown Turnout Address.
101     *
102     * @return CanMessage with the Thrown Address
103     */    
104    public CanMessage getAddrThrown(){
105        CanMessage m;
106        if (getInverted()){
107            m = addrClosed.makeMessage(tc.getCanid());
108        } else {
109            m = addrThrown.makeMessage(tc.getCanid());
110        }
111        return m;
112    }
113    
114    /**
115     * Package method returning CanMessage for the Closed Turnout Address
116     * @return CanReply with the Closed Address
117     */    
118    public CanMessage getAddrClosed(){
119        CanMessage m;
120        if (getInverted()){
121            m = addrThrown.makeMessage(tc.getCanid());
122        } else {
123            m = addrClosed.makeMessage(tc.getCanid());
124        }
125        return m;
126    }
127    
128    // note we only respond to addrThrown matches, 
129    // ie the left hand side of split of address "+5;+7"
130    // there's a response expected from 5
131    private void sendResponseToQuery(){
132        CanMessage m = null;
133        if (getCommandedState() == Turnout.THROWN) {
134            m=getAddrThrown(); // has already had inverted status considered
135            if (CbusMessage.isShort(m)) {
136                if (CbusMessage.getEventType(m)==CbusConstants.EVENT_ON) {
137                    m.setOpCode(CbusConstants.CBUS_ARSON);
138                }
139                else {
140                    m.setOpCode(CbusConstants.CBUS_ARSOF);
141                }
142            } else {
143                if (CbusMessage.getEventType(m)==CbusConstants.EVENT_ON) {
144                    m.setOpCode(CbusConstants.CBUS_ARON);
145                }
146                else {
147                    m.setOpCode(CbusConstants.CBUS_AROF);
148                }
149            }
150        } else if (getCommandedState() == Turnout.CLOSED){
151            m=getAddrThrown(); // has already had inverted status considered
152            if (CbusMessage.isShort(m)) {
153                if (CbusMessage.getEventType(m)==CbusConstants.EVENT_ON) {
154                    m.setOpCode(CbusConstants.CBUS_ARSOF);
155                }
156                else {
157                    m.setOpCode(CbusConstants.CBUS_ARSON);
158                }
159            } else {
160                if (CbusMessage.getEventType(m)==CbusConstants.EVENT_ON) {
161                    m.setOpCode(CbusConstants.CBUS_AROF);
162                }
163                else {
164                    m.setOpCode(CbusConstants.CBUS_ARON);
165                }
166            }
167        }
168        if (m!=null) {
169            CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
170            tc.sendCanMessage(m, this);
171        }
172    }
173    
174    /**
175     * {@inheritDoc}
176     *
177     * @see jmri.jmrix.can.CanListener#message(jmri.jmrix.can.CanMessage)
178     */
179    @Override
180    public void message(CanMessage f) {
181        if ( f.isExtended() || f.isRtr() ) {
182            return;
183        }
184        if (addrThrown.match(f)) {
185            int state = (!getInverted() ? THROWN : CLOSED);
186            newCommandedState(state);
187            if (_activeFeedbackType == DIRECT) {
188                newKnownState(state);
189            } else if (_activeFeedbackType == DELAYED) {
190                newKnownState(INCONSISTENT);
191                jmri.util.ThreadingUtil.runOnLayoutDelayed( () -> { newKnownState(state); },DELAYED_FEEDBACK_INTERVAL );
192            }
193        } else if (addrClosed.match(f)) {
194            int state = (!getInverted() ? CLOSED : THROWN);
195            newCommandedState(state);
196            if (_activeFeedbackType == DIRECT) {
197                newKnownState(state);
198            } else if (_activeFeedbackType == DELAYED) {
199                newKnownState(INCONSISTENT);
200                jmri.util.ThreadingUtil.runOnLayoutDelayed( () -> { newKnownState(state); },DELAYED_FEEDBACK_INTERVAL );
201            }
202        }
203    }
204    
205    /**
206     * {@inheritDoc}
207     *
208     * @see jmri.jmrix.can.CanListener#reply(jmri.jmrix.can.CanReply)
209     */
210    @Override
211    public void reply(CanReply origf) {
212        if ( origf.extendedOrRtr()) {
213            return;
214        }
215        // convert response events to normal
216        CanReply f = CbusMessage.opcRangeToStl(origf);
217        if (addrThrown.match(f)) {
218            int state = (!getInverted() ? THROWN : CLOSED);
219            newCommandedState(state);
220            if (_activeFeedbackType == DIRECT) {
221                newKnownState(state);
222            } else if (_activeFeedbackType == DELAYED) {
223                newKnownState(INCONSISTENT);
224                jmri.util.ThreadingUtil.runOnLayoutDelayed( () -> { newKnownState(state); },DELAYED_FEEDBACK_INTERVAL );
225            }
226        } else if (addrClosed.match(f)) {
227            int state = (!getInverted() ? CLOSED : THROWN);
228            newCommandedState(state);
229            if (_activeFeedbackType == DIRECT) {
230                newKnownState(state);
231            } else if (_activeFeedbackType == DELAYED) {
232                newKnownState(INCONSISTENT);
233                jmri.util.ThreadingUtil.runOnLayoutDelayed( () -> { newKnownState(state); },DELAYED_FEEDBACK_INTERVAL );
234            }
235        } else if (addrThrown.matchRequest(f)) {
236            sendResponseToQuery();
237        }
238    }
239    
240    /**
241     * {@inheritDoc}
242     */
243    @Override
244    protected void turnoutPushbuttonLockout(boolean locked) {
245    }
246    
247    /**
248     * {@inheritDoc}
249     */
250    @Override
251    public boolean canInvert() {
252        return true;
253    }
254    
255    /**
256     * {@inheritDoc}
257     */
258    @Override
259    public CanMessage getBeanOnMessage(){
260        return checkEvent(getAddrClosed());
261    }
262
263    /**
264     * {@inheritDoc}
265     */
266    @Override
267    public CanMessage getBeanOffMessage(){
268        return checkEvent(getAddrThrown());
269    }
270
271    /**
272     * {@inheritDoc}
273     */
274    @Override
275    public void dispose() {
276        tc.removeCanListener(this);
277        super.dispose();
278    }    
279
280    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusTurnout.class);
281
282}