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                newKnownStateDelayed(state);
191            }
192        } else if (addrClosed.match(f)) {
193            int state = (!getInverted() ? CLOSED : THROWN);
194            newCommandedState(state);
195            if (_activeFeedbackType == DIRECT) {
196                newKnownState(state);
197            } else if (_activeFeedbackType == DELAYED) {
198                newKnownStateDelayed(state);
199            }
200        }
201    }
202    
203    /**
204     * {@inheritDoc}
205     *
206     * @see jmri.jmrix.can.CanListener#reply(jmri.jmrix.can.CanReply)
207     */
208    @Override
209    public void reply(CanReply origf) {
210        if ( origf.extendedOrRtr()) {
211            return;
212        }
213        // convert response events to normal
214        CanReply f = CbusMessage.opcRangeToStl(origf);
215        if (addrThrown.match(f)) {
216            int state = (!getInverted() ? THROWN : CLOSED);
217            newCommandedState(state);
218            if (_activeFeedbackType == DIRECT) {
219                newKnownState(state);
220            } else if (_activeFeedbackType == DELAYED) {
221                newKnownStateDelayed(state);
222            }
223        } else if (addrClosed.match(f)) {
224            int state = (!getInverted() ? CLOSED : THROWN);
225            newCommandedState(state);
226            if (_activeFeedbackType == DIRECT) {
227                newKnownState(state);
228            } else if (_activeFeedbackType == DELAYED) {
229                newKnownStateDelayed(state);
230            }
231        } else if (addrThrown.matchRequest(f)) {
232            sendResponseToQuery();
233        }
234    }
235    
236    /**
237     * {@inheritDoc}
238     */
239    @Override
240    protected void turnoutPushbuttonLockout(boolean locked) {
241    }
242    
243    /**
244     * {@inheritDoc}
245     */
246    @Override
247    public boolean canInvert() {
248        return true;
249    }
250    
251    /**
252     * {@inheritDoc}
253     */
254    @Override
255    public CanMessage getBeanOnMessage(){
256        return checkEvent(getAddrClosed());
257    }
258
259    /**
260     * {@inheritDoc}
261     */
262    @Override
263    public CanMessage getBeanOffMessage(){
264        return checkEvent(getAddrThrown());
265    }
266
267    /**
268     * {@inheritDoc}
269     */
270    @Override
271    public void dispose() {
272        tc.removeCanListener(this);
273        super.dispose();
274    }    
275
276    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusTurnout.class);
277
278}