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