001package jmri.jmrix.can.cbus.simulator;
002
003import java.util.ArrayList;
004import jmri.jmrix.AbstractMessage;
005import jmri.jmrix.can.CanReply;
006import jmri.jmrix.can.CanSystemConnectionMemo;
007import jmri.jmrix.can.cbus.CbusConstants;
008import jmri.jmrix.can.cbus.swing.simulator.CsPane;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Simulating a MERG CBUS Command Station.
014 * Can operate as stand-alone or managed via @CbusSimulator.
015 *
016 * @author Steve Young Copyright (C) 2018 2019
017 * @see CbusSimulator
018 * @see CbusDummyCSSession
019 * @since 4.15.2
020 */
021public class CbusDummyCS extends CbusSimCanListener {
022    
023    private ArrayList<CbusDummyCSSession> _csSessions;
024    private int _simType;
025    private int _maxSessions;
026    private int _currentSessions;
027    private boolean _trackOn;
028    private boolean _estop;
029    private CsPane _pane;
030    
031    public ArrayList<String> csTypes = new ArrayList<>();
032    public ArrayList<String> csTypesTip = new ArrayList<>();
033    
034    protected static int DEFAULT_CS_TIMEOUT = 60000; // ms
035    protected static int DEFAULT_SESSION_START_SPDDIR = 128;  // default DCC speed direction on start session
036    
037    public CbusDummyCS( CanSystemConnectionMemo sysmemo ){
038        super(sysmemo,null);
039        init();
040    }
041
042    private void init() {
043        
044        _csSessions = new ArrayList<>();
045        _maxSessions = 32;
046        _currentSessions = 0;
047        setDelay(100);
048        _trackOn = true;
049        _estop = false;
050        _pane = null;
051
052        csTypes.add(Bundle.getMessage("cSDisabled"));
053        csTypesTip.add(null);
054        csTypes.add(Bundle.getMessage("csStandard"));
055        csTypesTip.add("Based on CANCMD v3");
056        setDummyType(1);
057    }
058
059    public int getNumberSessions(){
060        return _currentSessions;
061    }
062    
063    public void resetCS() {
064        for ( int i=0 ; (i < _csSessions.size()) ; i++) {
065            destroySession(_csSessions.get(i));
066        }
067        _csSessions = null;
068        _csSessions = new ArrayList<>();
069        _currentSessions = 0;
070        if ( _pane  != null ){
071            _pane.setNumSessions(_currentSessions);
072        }
073    }
074    
075    public void setDummyType(int type){
076        _simType = type;
077        if ( type == 0 ){
078            resetCS();
079        }
080        log.info("Simulated Command Station: {}", csTypes.get(_simType) );
081    }
082    
083    public int getDummyType() {
084        return _simType;
085    }
086    
087    public void setPane(CsPane pane) {
088        _pane = pane;
089    }
090    
091    // move to private in future
092    public boolean getResponseRSTAT() {
093        log.debug("estop {}",_estop);
094        return false;
095    } 
096    
097    private int getNextSession() {
098        log.debug("max sessions {}",_maxSessions);
099        ArrayList<Integer> nxtSessionList = new ArrayList<>();
100        for ( int i=0 ; (i < _csSessions.size()) ; i++) {
101            nxtSessionList.add(_csSessions.get(i).getSessionNum());
102        }
103        for ( int i=1 ; (i < 257 ) ; i++) {
104            if (!nxtSessionList.contains(i)){
105                return i;
106            }
107        }            
108        return 1000;
109    }
110    
111    private void setTrackPower(Boolean trueorfalse) {
112        _trackOn = trueorfalse;
113        CanReply r = new CanReply(1); // num elements
114        if (_trackOn) {
115            r.setElement(0, CbusConstants.CBUS_TON);
116        } else {
117            r.setElement(0, CbusConstants.CBUS_TOF);
118        }
119        send.sendWithDelay(r,getSendIn(),getSendOut(),getDelay());
120    }
121
122    protected void setEstop(Boolean estop) {
123        _estop = estop;
124        if (_estop) {
125            CanReply r = new CanReply(1);
126            r.setElement(0, CbusConstants.CBUS_ESTOP);
127           send.sendWithDelay(r,getSendIn(),getSendOut(),getDelay());
128            for ( int i=0 ; (i < _csSessions.size()) ; i++) {
129                _csSessions.get(i).setSpd(1);
130            }
131        }
132    }        
133    
134    private int getExistingSession( int rcvdIntAddr, Boolean rcvdIsLong ) {
135        for ( int i=0 ; (i < _csSessions.size()) ; i++) {
136            if ( 
137                ( _csSessions.get(i).getrcvdIntAddr() == rcvdIntAddr ) &&
138                ( _csSessions.get(i).getisLong().equals(rcvdIsLong) )  && 
139                ( _csSessions.get(i).getIsDispatched() == false ) 
140            ) {
141                return _csSessions.get(i).getSessionNum();
142            }
143        }
144        return -1;
145    }
146    
147    private void processrloc( int rcvdIntAddr, Boolean rcvdIsLong ) {
148        
149        // check for existing session
150        int exSession = getExistingSession( rcvdIntAddr, rcvdIsLong );
151        if ( exSession > -1 )  {
152            int locoaddr = rcvdIntAddr;
153            if (rcvdIsLong) {
154                locoaddr = locoaddr | 0xC000;
155            }
156            CanReply r = new CanReply(4);
157            r.setElement(0, CbusConstants.CBUS_ERR);
158            r.setElement(1, (locoaddr / 256)); // addr hi
159            r.setElement(2, locoaddr & 0xff);  // addr low
160            r.setElement(3, 2);
161            send.sendWithDelay(r,getSendIn(),getSendOut(),getDelay());
162            return;
163        }
164
165        int sessionid=getNextSession();
166        
167        CbusDummyCSSession session = new CbusDummyCSSession( this, sessionid,rcvdIntAddr, rcvdIsLong);
168        _csSessions.add(session);
169        _currentSessions++;
170        if ( _pane  != null ){
171            _pane.setNumSessions(_currentSessions);
172        }
173        session.sendPloc();
174    }
175    
176    private void processQloc( int session ) {
177        for ( int i=0 ; (i < _csSessions.size()) ; i++) {
178            if ( _csSessions.get(i).getSessionNum() == session  && (
179            !_csSessions.get(i).getIsDispatched() )) {
180                _csSessions.get(i).sendPloc();
181                return;
182            }
183        }
184        CanReply r = new CanReply(4);
185        r.setElement(0, CbusConstants.CBUS_ERR);
186        r.setElement(1, session);
187        r.setElement(2, 0);
188        r.setElement(3, 3);
189        send.sendWithDelay(r,getSendIn(),getSendOut(),getDelay());
190    }
191    
192    private void processDspd ( int session, int speeddir) {
193        for ( int i=0 ; (i < _csSessions.size()) ; i++) {
194            if ( _csSessions.get(i).getSessionNum() == session  && (
195            !_csSessions.get(i).getIsDispatched() ) ) {
196                _csSessions.get(i).setSpd(speeddir);
197                if ((speeddir & 0x7f) != 1) {
198                    setEstop(false);
199                }
200                return;
201            }
202        }
203        CanReply r = new CanReply(4);
204        r.setElement(0, CbusConstants.CBUS_ERR);
205        r.setElement(1, session);
206        r.setElement(2, 0);
207        r.setElement(3, 3);
208        send.sendWithDelay(r,getSendIn(),getSendOut(),getDelay());
209    }
210    
211    private void processDkeep ( int session ) {
212        for ( int i=0 ; (i < _csSessions.size()) ; i++) {
213            if ( ( _csSessions.get(i).getSessionNum() == session ) && (
214            !_csSessions.get(i).getIsDispatched() )) {
215                _csSessions.get(i).keepAlive();
216                return;
217            }
218        }
219        // send error if no session present
220        CanReply r = new CanReply(4);
221        r.setElement(0, CbusConstants.CBUS_ERR);
222        r.setElement(1, session);
223        r.setElement(2, 0);
224        r.setElement(3, 3);
225        send.sendWithDelay(r,getSendIn(),getSendOut(),getDelay());
226    }
227
228    protected void destroySession (CbusDummyCSSession session) {
229        for ( int i=0 ; (i < _csSessions.size()) ; i++) {
230            if ( _csSessions.get(i) == session ) {
231                
232                _csSessions.get(i).dispose();
233                
234                _csSessions.set(i, null);
235                _csSessions.remove(i);
236                _currentSessions--;
237                if ( _pane  != null ){
238                    _pane.setNumSessions(_currentSessions);
239                }
240                return;
241            }
242        }
243        log.error("session not found to destroy");
244    }
245
246    private void processKloc ( int session ) {
247        for ( int i=0 ; (i < _csSessions.size()) ; i++) {
248            if ( _csSessions.get(i).getSessionNum() == session  && (
249            !_csSessions.get(i).getIsDispatched() )) {
250                destroySession(_csSessions.get(i) );
251                return;
252            }
253        }
254        // session not present error sent
255        CanReply r = new CanReply(4);
256        r.setElement(0, CbusConstants.CBUS_ERR);
257        r.setElement(1, session);
258        r.setElement(2, 0);
259        r.setElement(3, 3);
260        send.sendWithDelay(r,getSendIn(),getSendOut(),getDelay());
261    }
262
263    /**
264     * {@inheritDoc}
265     */
266    @Override
267    protected void startProcessFrame(AbstractMessage m) {
268        // log.warn("dummy node canframe {}",m);
269        if ( getDummyType() == 0 ) {
270            return;
271        }
272        int opc = m.getElement(0);
273        int session = m.getElement(1);
274        switch (opc) {
275            case CbusConstants.CBUS_RTON:
276                setTrackPower(true);
277                break;
278            case CbusConstants.CBUS_RTOF:
279                setTrackPower(false);
280                break;
281            case CbusConstants.CBUS_RESTP:
282                setEstop(true);
283                break;
284            case CbusConstants.CBUS_RLOC:
285                int rcvdIntAddr = (m.getElement(1) & 0x3f) * 256 + m.getElement(2);
286                boolean rcvdIsLong = (m.getElement(1) & 0xc0) != 0;
287                processrloc(rcvdIntAddr,rcvdIsLong);
288                break;
289            case CbusConstants.CBUS_QLOC:
290                processQloc( session );
291                break;
292            case CbusConstants.CBUS_DSPD:
293                processDspd( session, m.getElement(2) );
294                break;
295            case CbusConstants.CBUS_DKEEP:
296                processDkeep( session );
297                break;
298            case CbusConstants.CBUS_KLOC:
299                processKloc( session );
300                break;
301            default:
302                break;
303        }
304    }
305    
306    /**
307     * {@inheritDoc}
308     */
309    @Override
310    public void dispose(){
311        super.dispose();
312        resetCS();
313    }
314
315    private static final Logger log = LoggerFactory.getLogger(CbusDummyCS.class);
316
317}