001package jmri.jmrix.can.cbus.swing.cbusslotmonitor;
002
003import java.util.ArrayList;
004import java.util.TimerTask;
005
006import javax.swing.JButton;
007
008import jmri.*;
009import jmri.jmrit.catalog.NamedIcon;
010import jmri.jmrix.can.CanListener;
011import jmri.jmrix.can.CanMessage;
012import jmri.jmrix.can.CanReply;
013import jmri.jmrix.can.CanSystemConnectionMemo;
014import jmri.jmrix.can.cbus.CbusConstants;
015import jmri.jmrix.can.cbus.CbusMessage;
016import jmri.jmrix.can.TrafficController;
017import jmri.util.swing.TextAreaFIFO;
018import jmri.util.ThreadingUtil;
019import jmri.util.TimerUtil;
020
021/**
022 * Table data model for display of CBUS Command Station Sessions and various Tools
023 *
024 * @author Steve Young (c) 2018 2019
025 * @see CbusSlotMonitorPane
026 *
027 */
028public class CbusSlotMonitorDataModel extends javax.swing.table.AbstractTableModel implements CanListener, Disposable  {
029
030    private final TextAreaFIFO tablefeedback;
031    private final TrafficController tc;
032    private final CanSystemConnectionMemo memo;
033    private final ArrayList<CbusSlotMonitorSession> _mainArray;
034
035    protected int _contype=0; //  pane console message type
036    protected String _context; // pane console text
037    private int cmndstat_fw =0; // command station firmware  TODO - get from node table
038
039    public static int CS_TIMEOUT = 2000; // command station timeout for estop and track messages
040    private static final int MAX_LINES = 5000;
041
042    // column order needs to match list in column tooltips
043    public static final int SESSION_ID_COLUMN = 0;
044    public static final int LOCO_ID_COLUMN = 1;
045    public static final int ESTOP_COLUMN = 2;
046    public static final int LOCO_ID_LONG_COLUMN = 3;
047    public static final int LOCO_COMMANDED_SPEED_COLUMN = 4;
048    public static final int LOCO_DIRECTION_COLUMN = 5;
049    public static final int FUNCTION_LIST = 6;
050    public static final int SPEED_STEP_COLUMN = 7;
051    public static final int LOCO_CONSIST_COLUMN = 8;
052    public static final int FLAGS_COLUMN = 9;
053    public static final int KILL_SESSION_COLUMN = 10;
054    public static final int LAUNCH_THROTTLE = 11;
055
056    public static final int MAX_COLUMN = 12;
057
058    static final int[] CBUSSLOTMONINITIALCOLS = {0,1,2,4,5,6,9,10,11};
059
060    /**
061     * Create a New CbusSlotMonitorDataModel.
062     * Public access for user scripting.
063     * @param memo CAN System Connection to monitor.
064     */
065    public CbusSlotMonitorDataModel(CanSystemConnectionMemo memo) {
066
067        _mainArray = new ArrayList<>(0);
068        tablefeedback = new TextAreaFIFO(MAX_LINES);
069        tablefeedback.setEditable ( false );
070
071        // connect to the CanInterface
072        tc = memo.getTrafficController();
073        addTc(tc);
074        this.memo = memo;
075        log.debug("Starting {} CbusSlotMonitorDataModel", memo.getUserName());
076
077    }
078
079    protected TextAreaFIFO tablefeedback(){
080        return tablefeedback;
081    }
082
083    // order needs to match column list top of tabledatamodel
084    static final String[] CBUSSLOTMONTOOLTIPS = {
085        ("Session ID"),
086        null, // loco id
087        null, // estop
088        ("If Loco ID heard by long address format"),
089        ("Speed Commanded by throttle / CAB"),
090        ("Forward or Reverse"),
091        ("Any Functions set to ON"),
092        ("Speed Steps"),
093        null, // consist id
094        null, // flags
095        Bundle.getMessage("ReleaseTip"),  // send KLOC
096        Bundle.getMessage("LaunchThrottleTip")
097
098    }; // Length = number of items in array should (at least) match number of columns
099
100    /**
101     * Return the number of rows to be displayed.
102     */
103    @Override
104    public int getRowCount() {
105        return _mainArray.size();
106    }
107
108    @Override
109    public int getColumnCount() {
110        return MAX_COLUMN;
111    }
112
113    /**
114     * Returns String of column name from column int
115     * used in table header
116     * @param col int col number
117     */
118    @Override
119    public String getColumnName(int col) {
120        switch (col) {
121            case SESSION_ID_COLUMN:
122                return Bundle.getMessage("OPC_SN"); // Session
123            case LOCO_ID_COLUMN:
124                return Bundle.getMessage("LocoID"); // Loco ID
125            case LOCO_ID_LONG_COLUMN:
126                return Bundle.getMessage("Long"); // Long
127            case LOCO_CONSIST_COLUMN:
128                return Bundle.getMessage("OPC_CA"); // Consist ID
129            case LOCO_DIRECTION_COLUMN:
130                return Bundle.getMessage("TrafficDirection"); // Direction
131            case LOCO_COMMANDED_SPEED_COLUMN:
132                return Bundle.getMessage("Speed");
133            case ESTOP_COLUMN:
134                return Bundle.getMessage("EStop");
135            case SPEED_STEP_COLUMN:
136                return Bundle.getMessage("Steps");
137            case FLAGS_COLUMN:
138                return Bundle.getMessage("OPC_FL"); // Flags
139            case FUNCTION_LIST:
140                return Bundle.getMessage("Functions");
141            case KILL_SESSION_COLUMN:
142                return Bundle.getMessage("Release");
143            case LAUNCH_THROTTLE:
144                return Bundle.getMessage("ThrottleTitle");
145            default:
146                return "unknown"; // NOI18N
147        }
148    }
149
150    /**
151     * {@inheritDoc}
152     */
153    @Override
154    public Class<?> getColumnClass(int col) {
155        switch (col) {
156            case SESSION_ID_COLUMN:
157            case LOCO_ID_COLUMN:
158            case LOCO_CONSIST_COLUMN:
159                return Integer.class;
160            case LOCO_ID_LONG_COLUMN:
161                return Boolean.class;
162            case LOCO_DIRECTION_COLUMN:
163            case FUNCTION_LIST:
164            case FLAGS_COLUMN:
165            case SPEED_STEP_COLUMN:
166            case LOCO_COMMANDED_SPEED_COLUMN:
167                return String.class;
168            case ESTOP_COLUMN:
169            case KILL_SESSION_COLUMN:
170            case LAUNCH_THROTTLE:
171                return JButton.class;
172            default:
173                return null;
174        }
175    }
176
177    /**
178     * {@inheritDoc}
179     */
180    @Override
181    public boolean isCellEditable(int row, int col) {
182        switch (col) {
183            case ESTOP_COLUMN:
184                return _mainArray.get(row).getSessionId() > 0;
185            case LAUNCH_THROTTLE:
186                return true;
187            case KILL_SESSION_COLUMN:
188                return isJmriManagedThrottle(_mainArray.get(row).getLocoAddr());
189            default:
190                return false;
191        }
192    }
193
194    /**
195     * {@inheritDoc}
196     */
197    @Override
198    public Object getValueAt(int row, int col) {
199        switch (col) {
200            case SESSION_ID_COLUMN:
201                if (_mainArray.get(row).getSessionId() > 0) {
202                    return _mainArray.get(row).getSessionId();
203                } else {
204                    return "";
205                }
206            case LOCO_ID_COLUMN:
207                return _mainArray.get(row).getLocoAddr().getNumber();
208            case LOCO_ID_LONG_COLUMN:
209                return _mainArray.get(row).getLocoAddr().isLongAddress();
210            case LOCO_CONSIST_COLUMN:
211                return _mainArray.get(row).getConsistId();
212            case FLAGS_COLUMN:
213                return _mainArray.get(row).getFlagString();
214            case LOCO_DIRECTION_COLUMN:
215                return _mainArray.get(row).getDirection();
216            case LOCO_COMMANDED_SPEED_COLUMN:
217                return _mainArray.get(row).getCommandedSpeed();
218            case ESTOP_COLUMN:
219                if ( _mainArray.get(row).getSessionId() > 0 ) { // is active session
220                    return new NamedIcon("resources/icons/throttles/estop.png", "resources/icons/throttles/estop.png");
221                }
222                return null; // disables button if action is not possible
223            case FUNCTION_LIST:
224                return _mainArray.get(row).getFunctionString();
225            case SPEED_STEP_COLUMN:
226                return _mainArray.get(row).getSpeedSteps();
227            case KILL_SESSION_COLUMN:
228                if ( isJmriManagedThrottle(_mainArray.get(row).getLocoAddr()) ) {
229                    return Bundle.getMessage("Release");
230                }
231                return null; // disables button if action is not possible
232            case LAUNCH_THROTTLE:
233                return Bundle.getMessage("ThrottleTitle");
234            default:
235                log.error("internal state inconsistent with table request for row {} col {}", row, col);
236                return null;
237        }
238    }
239
240    /**
241     * {@inheritDoc}
242     */
243    @Override
244    public void setValueAt(Object value, int row, int col) {
245        // log.debug("427 set valueat called row: {} col: {}", row, col);
246        switch (col) {
247            case SESSION_ID_COLUMN:
248                _mainArray.get(row).setSessionId( (Integer) value );
249                updateGui(row,col);
250                updateGui(row, KILL_SESSION_COLUMN);
251                break;
252            case LOCO_CONSIST_COLUMN:
253                _mainArray.get(row).setConsistId( (Integer) value );
254                updateGui(row,col);
255                break;
256            case LOCO_COMMANDED_SPEED_COLUMN:
257                _mainArray.get(row).setDccSpeed( (Integer) value );
258                updateGui(row,col);
259                updateGui(row,LOCO_DIRECTION_COLUMN);
260                updateMemory(_mainArray.get(row));
261                break;
262            case ESTOP_COLUMN:
263                int stopspeed=1;
264                if ( _mainArray.get(row).getDirection().equals(Bundle.getMessage("FWD") )
265                    && _mainArray.get(row).getSpeedSteps().equals("128") ) {
266                    stopspeed=129;
267                }
268                CanMessage m = new CanMessage(tc.getCanid());
269                m.setNumDataElements(3);
270                CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
271                m.setElement(0, CbusConstants.CBUS_DSPD);
272                m.setElement(1, _mainArray.get(row).getSessionId() );
273                m.setElement(2, stopspeed);
274                tc.sendCanMessage(m, null);
275                break;
276            case SPEED_STEP_COLUMN:
277                _mainArray.get(row).setSpeedSteps( (String) value );
278                updateGui(row,col);
279                break;
280            case KILL_SESSION_COLUMN:
281                CanMessage msg = new CanMessage(2, tc.getCanid());
282                CbusMessage.setPri(msg, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
283                msg.setOpCode(CbusConstants.CBUS_KLOC);
284                msg.setElement(1, _mainArray.get(row).getSessionId());
285                tc.sendCanMessage(msg, null);
286                break;
287            case LAUNCH_THROTTLE:
288                var tf = InstanceManager.getDefault(jmri.jmrit.throttle.ThrottleFrameManager.class).createThrottleFrame();
289                tf.toFront();
290                tf.getAddressPanel().setCurrentAddress(_mainArray.get(row).getLocoAddr() );
291                break;
292            default:
293                log.warn("Failed to set value at column {}",col);
294                break;
295        }
296    }
297
298    private void updateGui(int row,int col) {
299        ThreadingUtil.runOnGUI( ()-> fireTableCellUpdated(row, col));
300    }
301
302    private boolean maintainLocoSpdMemory = false;
303
304    /**
305     * Set true to maintain a Memory Variable for the speed of each loco.
306     * Note this is an experimental method ( 5.5.5 ) and may be subject to change.
307     * <p>
308     * The Memory System Name is in the form e.g. IM12(S) or IM789(L)
309     * i.e. Internal Memory Loco 12, Short address.
310     * It may be easier to refer to this Memory in Jython scripts
311     * by giving it a User Name.
312     * <p>
313     * The Memory Value is the commanded Loco speed, 0-126.
314     * 0 includes a normal stop and e-stop.
315     * <p>
316     * The Value updates whenever a Loco speed command is heard on the
317     * connection hence not restricted to this JMRI instance.
318     * @since 5.5.5
319     * @param newVal true to enable updates, false to stop updates.
320     *               Default is false, no updates provided.
321     */
322    public void setMaintainLocoSpdMemory(boolean newVal) {
323        maintainLocoSpdMemory = newVal;
324    }
325
326    private void updateMemory(CbusSlotMonitorSession session){
327        if ( !maintainLocoSpdMemory || session==null ){
328            return;
329        }
330        MemoryManager memMgr = InstanceManager.getDefault(MemoryManager.class);
331        memMgr.provideMemory( memMgr.getSystemNamePrefix() + session.getLocoAddr() ).setValue(
332            jmri.util.StringUtil.getFirstIntFromString(session.getCommandedSpeed()));
333    }
334
335    private int createnewrow(int locoid, Boolean islong){
336
337        DccLocoAddress addr = new DccLocoAddress(locoid,islong );
338        CbusSlotMonitorSession newSession = new CbusSlotMonitorSession(addr);
339
340        _mainArray.add(newSession);
341        fireTableRowsInserted((getRowCount()-1), (getRowCount()-1));
342        return getRowCount()-1;
343    }
344
345    // returning the row number not the session
346    // so that any updates go through the table model
347    // and are updated in the GUI
348    private int provideTableRow( DccLocoAddress addr ) {
349        for (int i = 0; i < getRowCount(); i++) {
350            if ( addr.equals(_mainArray.get(i).getLocoAddr() ) )  {
351                return i;
352            }
353        }
354        return createnewrow(addr.getNumber(),addr.isLongAddress());
355    }
356
357    private int getrowfromsession(int sessionid){
358        for (int i = 0; i < getRowCount(); i++) {
359            if (sessionid==_mainArray.get(i).getSessionId() )  {
360                return i;
361            }
362        }
363        // no row so request session details from command station
364        CanMessage m = new CanMessage(tc.getCanid());
365        m.setNumDataElements(2);
366        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
367        m.setElement(0, CbusConstants.CBUS_QLOC);
368        m.setElement(1, sessionid);
369        tc.sendCanMessage(m, null);
370        // should receive a PLOC response with loco id etc.
371        return -1;
372    }
373
374    /**
375     * @param m outgoing CanMessage
376     */
377    @Override
378    public void message(CanMessage m) {
379        if ( m.extendedOrRtr() ) {
380            return;
381        }
382        int opc = CbusMessage.getOpcode(m);
383        // process is false as outgoing
384        switch (opc) {
385            case CbusConstants.CBUS_PLOC:
386                {
387                    int rcvdIntAddr = (m.getElement(2) & 0x3f) * 256 + m.getElement(3);
388                    boolean rcvdIsLong = (m.getElement(2) & 0xc0) != 0;
389                    processploc(m.getElement(1),new DccLocoAddress(rcvdIntAddr,rcvdIsLong),m.getElement(4),
390                            m.getElement(5),m.getElement(6),m.getElement(7));
391                    break;
392                }
393            case CbusConstants.CBUS_RLOC:
394                {
395                    int rcvdIntAddr = (m.getElement(1) & 0x3f) * 256 + m.getElement(2);
396                    boolean rcvdIsLong = (m.getElement(1) & 0xc0) != 0;
397                    processrloc(false,new DccLocoAddress(rcvdIntAddr,rcvdIsLong));
398                    break;
399                }
400            case CbusConstants.CBUS_DSPD:
401                processdspd(m.getElement(1),m.getElement(2));
402                break;
403            case CbusConstants.CBUS_DKEEP:
404                processdkeep(m.getElement(1));
405                break;
406            case CbusConstants.CBUS_KLOC:
407                processkloc(false,m.getElement(1));
408                break;
409            case CbusConstants.CBUS_GLOC:
410                int rcvdIntAddr = (m.getElement(1) & 0x3f) * 256 + m.getElement(2);
411                boolean rcvdIsLong = (m.getElement(1) & 0xc0) != 0;
412                processgloc(false,new DccLocoAddress(rcvdIntAddr,rcvdIsLong),m.getElement(3));
413                break;
414            case CbusConstants.CBUS_ERR:
415                processerr(false,m.getElement(1),m.getElement(2),m.getElement(3));
416                break;
417            case CbusConstants.CBUS_STMOD:
418                processstmod(false,m.getElement(1),m.getElement(2));
419                break;
420            case CbusConstants.CBUS_DFUN:
421                processdfun(m.getElement(1),m.getElement(2),m.getElement(3));
422                break;
423            case CbusConstants.CBUS_DFNON:
424                processdfnon(m.getElement(1),m.getElement(2),true);
425                break;
426            case CbusConstants.CBUS_DFNOF:
427                processdfnon(m.getElement(1),m.getElement(2),false); // same routine as DFNON
428                break;
429            case CbusConstants.CBUS_PCON:
430                processpcon(m.getElement(1),m.getElement(2));
431                break;
432            case CbusConstants.CBUS_KCON:
433                processpcon(m.getElement(1),0); // same routine as PCON
434                break;
435            case CbusConstants.CBUS_DFLG:
436                processdflg(m.getElement(1),m.getElement(2));
437                break;
438            case CbusConstants.CBUS_ESTOP:
439                processestop();
440                break;
441            case CbusConstants.CBUS_RTON:
442                processrton();
443                break;
444            case CbusConstants.CBUS_RTOF:
445                processrtof();
446                break;
447            case CbusConstants.CBUS_TON:
448                processton();
449                break;
450            case CbusConstants.CBUS_TOF:
451                processtof();
452                break;
453            default:
454                break;
455        }
456    }
457
458    /**
459     * @param m incoming cbus CanReply
460     */
461    @Override
462    public void reply(CanReply m) {
463        if ( m.extendedOrRtr() ) {
464            return;
465        }
466        int opc = CbusMessage.getOpcode(m);
467        int rcvdIntAddr;
468        boolean rcvdIsLong;
469        DccLocoAddress addr;
470        switch (opc) {
471            case CbusConstants.CBUS_STAT:
472                // todo more on this when finished tested v3 firmware with all opcs
473                // for now, if a stat opc is received then it's v4
474                // no stat received when < v4 Firmware
475                cmndstat_fw = 4;
476                break;
477            case CbusConstants.CBUS_PLOC:
478                rcvdIntAddr = (m.getElement(2) & 0x3f) * 256 + m.getElement(3);
479                rcvdIsLong = (m.getElement(2) & 0xc0) != 0;
480                addr = new DccLocoAddress(rcvdIntAddr,rcvdIsLong);
481                processploc(m.getElement(1),addr,m.getElement(4),
482                        m.getElement(5),m.getElement(6),m.getElement(7));
483                break;
484            case CbusConstants.CBUS_RLOC:
485                rcvdIntAddr = (m.getElement(1) & 0x3f) * 256 + m.getElement(2);
486                rcvdIsLong = (m.getElement(1) & 0xc0) != 0;
487                addr = new DccLocoAddress(rcvdIntAddr,rcvdIsLong);
488                processrloc(true,addr);
489                break;
490            case CbusConstants.CBUS_DSPD:
491                processdspd(m.getElement(1),m.getElement(2));
492                break;
493            case CbusConstants.CBUS_DKEEP:
494                processdkeep(m.getElement(1));
495                break;
496            case CbusConstants.CBUS_KLOC:
497                processkloc(true,m.getElement(1));
498                break;
499            case CbusConstants.CBUS_GLOC:
500                rcvdIntAddr = (m.getElement(1) & 0x3f) * 256 + m.getElement(2);
501                rcvdIsLong = (m.getElement(1) & 0xc0) != 0;
502                addr = new DccLocoAddress(rcvdIntAddr,rcvdIsLong);
503                processgloc(true,addr,m.getElement(3));
504                break;
505            case CbusConstants.CBUS_ERR:
506                processerr(true,m.getElement(1),m.getElement(2),m.getElement(3));
507                break;
508            case CbusConstants.CBUS_STMOD:
509                processstmod(true,m.getElement(1),m.getElement(2));
510                break;
511            case CbusConstants.CBUS_DFUN:
512                processdfun(m.getElement(1),m.getElement(2),m.getElement(3));
513                break;
514            case CbusConstants.CBUS_DFNON:
515                processdfnon(m.getElement(1),m.getElement(2),true);
516                break;
517            case CbusConstants.CBUS_DFNOF:
518                processdfnon(m.getElement(1),m.getElement(2),false);  // same routine as DFNON
519                break;
520            case CbusConstants.CBUS_PCON:
521                processpcon(m.getElement(1),m.getElement(2));
522                break;
523            case CbusConstants.CBUS_KCON:
524                processpcon(m.getElement(1),0); // same routine as PCON
525                break;
526            case CbusConstants.CBUS_DFLG:
527                processdflg(m.getElement(1),m.getElement(2));
528                break;
529            case CbusConstants.CBUS_ESTOP:
530                processestop();
531                break;
532            case CbusConstants.CBUS_RTON:
533                processrton();
534                break;
535            case CbusConstants.CBUS_RTOF:
536                processrtof();
537                break;
538            case CbusConstants.CBUS_TON:
539                processton();
540                break;
541            case CbusConstants.CBUS_TOF:
542                processtof();
543                break;
544            default:
545                break;
546        }
547    }
548
549    // ploc sent from a command station to a throttle
550    private void processploc( int session, DccLocoAddress addr,
551        int speeddir, int fa, int fb, int fc) {
552
553        int row = provideTableRow(addr);
554        setValueAt(session, row, SESSION_ID_COLUMN);
555        setValueAt(speeddir, row, LOCO_COMMANDED_SPEED_COLUMN);
556        processdfun( session, 1, fa);
557        processdfun( session, 2, fb);
558        processdfun( session, 3, fc);
559    }
560
561    // kloc sent from throttle to command station to release loco, which will continue at current speed
562    private void processkloc(boolean messagein, int session) {
563        int row=getrowfromsession(session);
564        String messagedir;
565        if (messagein){ // external throttle
566            messagedir = Bundle.getMessage("CBUS_IN_CAB");
567        } else { // jmri throttle
568            messagedir = Bundle.getMessage("CBUS_OUT_CMD");
569        }
570        log.debug("direction {} kloc {}",messagedir,Bundle.getMessage("CNFO_KLOC",session));
571        if ( row > -1 ) {
572            setValueAt(0, row, SESSION_ID_COLUMN); // Session restored by sending QLOC if v4 firmware
573
574            // version 4 fw maintains version number, so to check this request session details from command station
575            // if this is sent with the v3 firmware then a popup error comes up from cbus throttlemanager when
576            // errStr is populated in the switch error clauses in canreply.
577            // check if version 4
578            if ( ( cmndstat_fw > 3 ) && ( !"0".startsWith(_mainArray.get(row).getCommandedSpeed()) )) {
579                log.debug("send qloc {} {}",Bundle.getMessage("CBUS_OUT_CMD"),Bundle.getMessage("QuerySession8a",session));
580                CanMessage m = new CanMessage(tc.getCanid());
581                m.setNumDataElements(2);
582                CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
583                m.setElement(0, CbusConstants.CBUS_QLOC);
584                m.setElement(1, session);
585                tc.sendCanMessage(m, null);
586            }
587        }
588    }
589
590    // rloc sent from throttle to command station to get loco
591    private void processrloc(boolean messagein, DccLocoAddress addr ) {
592        int row = provideTableRow(addr);
593        log.debug("{} new table row {}", messagein,row);
594    }
595
596    // gloc sent from throttle to command station to get loco
597    private void processgloc(boolean messagein, DccLocoAddress addr, int flags) {
598        int row = provideTableRow(addr);
599        log.debug ("processgloc row {}",row);
600        StringBuilder flagstring = new StringBuilder();
601        if (messagein){ // external throttle
602            flagstring.append(Bundle.getMessage("CBUS_IN_CAB"));
603        } else { // jmri throttle
604            flagstring.append(Bundle.getMessage("CBUS_OUT_CMD"));
605        }
606
607        boolean stealmode = ((flags ) & 1) != 0;
608        boolean sharemode = ((flags >> 1 ) & 1) != 0;
609        if (stealmode){
610            flagstring.append(Bundle.getMessage("CNFO_GLOC_ST"));
611        }
612        else if (sharemode){
613            flagstring.append(Bundle.getMessage("CNFO_GLOC_SH"));
614        }
615        else {
616            flagstring.append(Bundle.getMessage("CNFO_GLOC"));
617        }
618        flagstring.append(addr);
619        addToLog(1,flagstring.toString());
620    }
621
622    // stmod sent from throttle to cmmnd station if speed steps not 128 / set service mode / sound mode
623    private void processstmod(boolean messagein, int session, int flags) {
624        int row=getrowfromsession(session);
625        if ( row > -1 ) {
626            String messagedir;
627            if (messagein){ // external throttle
628                messagedir=( Bundle.getMessage("CBUS_IN_CAB"));
629            } else { // jmri throttle
630                messagedir=( Bundle.getMessage("CBUS_OUT_CMD"));
631            }
632
633            boolean sm0 = ((flags ) & 1) != 0;
634            boolean sm1 = ((flags >> 1 ) & 1) != 0;
635            boolean servicemode = ((flags >> 2 ) & 1) != 0;
636            boolean soundmode = ((flags >> 3 ) & 1) != 0;
637
638            String speedstep="";
639            if ((!sm0) && (!sm1)){
640                speedstep="128";
641            }
642            else if ((!sm0) && (sm1)){
643                speedstep="14";
644            }
645            else if ((sm0) && (!sm1)){
646                speedstep="28I";
647            }
648            else if ((sm0) && (sm1)){
649                speedstep="28";
650            }
651            log.debug("processstmod {} {}",messagedir,Bundle.getMessage("CNFO_STMOD",session,speedstep,servicemode,soundmode));
652            setValueAt(speedstep, row, SPEED_STEP_COLUMN);
653        }
654    }
655
656    // DKEEP sent as keepalive from throttle to command station
657    private void processdkeep(int session) {
658        int row=getrowfromsession(session);
659        if ( row < 0 ) {
660            log.debug("Requesting loco details for session {}.",session );
661        }
662    }
663
664    // DSPD sent from throttle to command station , speed / direction
665    private void processdspd( int session, int speeddir) {
666        int row=getrowfromsession(session);
667        if ( row > -1 ) {
668            setValueAt(speeddir, row, LOCO_COMMANDED_SPEED_COLUMN);
669        }
670    }
671
672    // DFLG sent from throttle to command station to notify engine change in flags
673    private void processdflg( int session, int flags) {
674        int row=getrowfromsession(session);
675        if ( row>-1 ) {
676            _mainArray.get(row).setFlags(flags);
677            updateGui(row,SPEED_STEP_COLUMN);
678            updateGui(row,FLAGS_COLUMN);
679        }
680    }
681
682    // DFNON Sent by a cab to turn on a specific loco function, alternative method to DFUN
683    // also used to process function responses from DFNOF
684    private void processdfnon( int session, int function, boolean trueorfalse) {
685        int row=getrowfromsession(session);
686        if ( row>-1 && function>-1 && function<29 ) {
687            _mainArray.get(row).setFunction(function,trueorfalse);
688            updateGui(row,FUNCTION_LIST);
689        }
690    }
691
692    // DFUN Sent by a cab to trigger loco function
693    // also used to process function responses from PLOC
694    private void processdfun( int session, int range, int functionbyte) {
695        int row=getrowfromsession(session);
696        if ( row > -1 ) {
697            switch (range) {
698                case 1:
699                    _mainArray.get(row).setFunction(0, ((functionbyte & CbusConstants.CBUS_F0) == CbusConstants.CBUS_F0));
700                    _mainArray.get(row).setFunction(1, ((functionbyte & CbusConstants.CBUS_F1) == CbusConstants.CBUS_F1));
701                    _mainArray.get(row).setFunction(2, ((functionbyte & CbusConstants.CBUS_F2) == CbusConstants.CBUS_F2));
702                    _mainArray.get(row).setFunction(3, ((functionbyte & CbusConstants.CBUS_F3) == CbusConstants.CBUS_F3));
703                    _mainArray.get(row).setFunction(4, ((functionbyte & CbusConstants.CBUS_F4) == CbusConstants.CBUS_F4));
704                    break;
705                case 2:
706                    _mainArray.get(row).setFunction(5, ((functionbyte & CbusConstants.CBUS_F5) == CbusConstants.CBUS_F5));
707                    _mainArray.get(row).setFunction(6, ((functionbyte & CbusConstants.CBUS_F6) == CbusConstants.CBUS_F6));
708                    _mainArray.get(row).setFunction(7, ((functionbyte & CbusConstants.CBUS_F7) == CbusConstants.CBUS_F7));
709                    _mainArray.get(row).setFunction(8, ((functionbyte & CbusConstants.CBUS_F8) == CbusConstants.CBUS_F8));
710                    break;
711                case 3:
712                    _mainArray.get(row).setFunction(9, ((functionbyte & CbusConstants.CBUS_F9) == CbusConstants.CBUS_F9));
713                    _mainArray.get(row).setFunction(10, ((functionbyte & CbusConstants.CBUS_F10) == CbusConstants.CBUS_F10));
714                    _mainArray.get(row).setFunction(11, ((functionbyte & CbusConstants.CBUS_F11) == CbusConstants.CBUS_F11));
715                    _mainArray.get(row).setFunction(12, ((functionbyte & CbusConstants.CBUS_F12) == CbusConstants.CBUS_F12));
716                    break;
717                case 4:
718                    _mainArray.get(row).setFunction(13, ((functionbyte & CbusConstants.CBUS_F13) == CbusConstants.CBUS_F13));
719                    _mainArray.get(row).setFunction(14, ((functionbyte & CbusConstants.CBUS_F14) == CbusConstants.CBUS_F14));
720                    _mainArray.get(row).setFunction(15, ((functionbyte & CbusConstants.CBUS_F15) == CbusConstants.CBUS_F15));
721                    _mainArray.get(row).setFunction(16, ((functionbyte & CbusConstants.CBUS_F16) == CbusConstants.CBUS_F16));
722                    _mainArray.get(row).setFunction(17, ((functionbyte & CbusConstants.CBUS_F17) == CbusConstants.CBUS_F17));
723                    _mainArray.get(row).setFunction(18, ((functionbyte & CbusConstants.CBUS_F18) == CbusConstants.CBUS_F18));
724                    _mainArray.get(row).setFunction(19, ((functionbyte & CbusConstants.CBUS_F19) == CbusConstants.CBUS_F19));
725                    _mainArray.get(row).setFunction(20, ((functionbyte & CbusConstants.CBUS_F20) == CbusConstants.CBUS_F20));
726                    break;
727                case 5:
728                    _mainArray.get(row).setFunction(21, ((functionbyte & CbusConstants.CBUS_F21) == CbusConstants.CBUS_F21));
729                    _mainArray.get(row).setFunction(22, ((functionbyte & CbusConstants.CBUS_F22) == CbusConstants.CBUS_F22));
730                    _mainArray.get(row).setFunction(23, ((functionbyte & CbusConstants.CBUS_F23) == CbusConstants.CBUS_F23));
731                    _mainArray.get(row).setFunction(24, ((functionbyte & CbusConstants.CBUS_F24) == CbusConstants.CBUS_F24));
732                    _mainArray.get(row).setFunction(25, ((functionbyte & CbusConstants.CBUS_F25) == CbusConstants.CBUS_F25));
733                    _mainArray.get(row).setFunction(26, ((functionbyte & CbusConstants.CBUS_F26) == CbusConstants.CBUS_F26));
734                    _mainArray.get(row).setFunction(27, ((functionbyte & CbusConstants.CBUS_F27) == CbusConstants.CBUS_F27));
735                    _mainArray.get(row).setFunction(28, ((functionbyte & CbusConstants.CBUS_F28) == CbusConstants.CBUS_F28));
736                    break;
737                default:
738                    break;
739            }
740            updateGui(row,FUNCTION_LIST);
741        }
742    }
743
744    // ERR sent by command station
745    private void processerr(boolean messagein, int one, int two, int errnum) {
746        int rcvdIntAddr = (one & 0x3f) * 256 + two;
747
748        StringBuilder buf = new StringBuilder();
749        if (messagein){ // external throttle
750            buf.append( Bundle.getMessage("CBUS_CMND_BR"));
751        } else { // jmri throttle
752            buf.append( Bundle.getMessage("CBUS_OUT_CMD"));
753        }
754
755        switch (errnum) {
756            case 1:
757                buf.append(Bundle.getMessage("ERR_LOCO_STACK_FULL"));
758                buf.append(rcvdIntAddr);
759                break;
760            case 2:
761                buf.append(Bundle.getMessage("ERR_LOCO_ADDRESS_TAKEN",rcvdIntAddr));
762                break;
763            case 3:
764                buf.append(Bundle.getMessage("ERR_SESSION_NOT_PRESENT",one));
765                break;
766            case 4:
767                buf.append(Bundle.getMessage("ERR_CONSIST_EMPTY"));
768                buf.append(one);
769                break;
770            case 5:
771                buf.append(Bundle.getMessage("ERR_LOCO_NOT_FOUND"));
772                buf.append(one);
773                break;
774            case 6:
775                buf.append(Bundle.getMessage("ERR_CAN_BUS_ERROR"));
776                break;
777            case 7:
778                buf.append(Bundle.getMessage("ERR_INVALID_REQUEST"));
779                buf.append(rcvdIntAddr);
780                break;
781            case 8:
782                buf.append(Bundle.getMessage("ERR_SESSION_CANCELLED",one));
783                // cancel session number in table
784                int row = getrowfromsession(one);
785                if ( row > -1 ) {
786                    setValueAt(0, row, SESSION_ID_COLUMN);
787                }
788                break;
789            default:
790                break;
791        }
792        _context = buf.toString();
793        addToLog(1,_context);
794    }
795
796    // PCON sent by throttle to add to consist
797    // also used to process remove from consist KCON
798    private void processpcon( int session, int consist){
799        log.debug("processing pcon");
800        int row=getrowfromsession(session);
801        if ( row>-1 ) {
802
803            int consistaddr = (consist & 0x7f);
804            setValueAt(consistaddr, row, LOCO_CONSIST_COLUMN);
805
806            StringBuilder buf = new StringBuilder();
807            buf.append( Bundle.getMessage("CNFO_PCON",session,consistaddr));
808            if ((consist & 0x80) == 0x80){
809                buf.append( Bundle.getMessage("FWD"));
810            } else {
811                buf.append( Bundle.getMessage("REV"));
812            }
813            addToLog(1,buf.toString() );
814        }
815    }
816
817    private void processestop(){
818        addToLog(1,"Command station acknowledges estop");
819        clearEStopTask();
820    }
821
822    private void processrton(){
823        setPowerTask();
824    }
825
826    private void processrtof(){
827        setPowerTask();
828    }
829
830    private void processton(){
831        clearPowerTask();
832        log.debug("Track on confirmed from command station.");
833    }
834
835    private void processtof(){
836        clearPowerTask();
837        log.debug("Track off confirmed from command station.");
838    }
839
840    public void sendcbusestop(){
841        log.info("Sending Command Station e-stop");
842        CanMessage m = new CanMessage(tc.getCanid());
843        m.setNumDataElements(1);
844        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
845        m.setElement(0, CbusConstants.CBUS_RESTP);
846        tc.sendCanMessage(m, null);
847
848        // start a timer to monitor if timeout, ie if command station doesn't respond
849        setEstopTask();
850    }
851
852    private transient TimerTask eStopTask;
853
854    private void clearEStopTask() {
855        if (eStopTask != null ) {
856            eStopTask.cancel();
857            eStopTask = null;
858        }
859    }
860
861    private void setEstopTask() {
862        eStopTask = new TimerTask() {
863            @Override
864            public void run() {
865                eStopTask = null;
866                addToLog(1,("Send Estop No Response received from command station."));
867                log.info("Send Estop No Response received from command station.");
868            }
869        };
870        TimerUtil.schedule(eStopTask, ( CS_TIMEOUT ) );
871    }
872
873    private transient TimerTask powerTask;
874
875    private void clearPowerTask() {
876        if (powerTask != null ) {
877            powerTask.cancel();
878            powerTask = null;
879        }
880    }
881
882    private void setPowerTask() {
883        powerTask = new TimerTask() {
884            @Override
885            public void run() {
886                powerTask = null;
887                addToLog(1,("Track Power - No Response received from command station."));
888                log.info("Track Power - No Response received from command station.");
889            }
890        };
891        TimerUtil.schedule(powerTask, ( CS_TIMEOUT ) );
892    }
893
894    /**
895     * Add to Slot Monitor Console Log
896     * @param cbuserror int
897     * @param cbustext String console message
898     */
899    public void addToLog(int cbuserror, String cbustext){
900        ThreadingUtil.runOnGUI( ()-> tablefeedback.append( System.lineSeparator()+cbustext));
901    }
902
903    private boolean isJmriManagedThrottle(LocoAddress addr) {
904        ThrottleManager tm = memo.getFromMap(ThrottleManager.class);
905        return tm != null && tm.getThrottleUsageCount(addr) > 0;
906    }
907
908    /**
909     * disconnect from the CBUS
910     */
911    @Override
912    public void dispose() {
913        removeTc(tc);
914
915        // stop timers if running
916        clearEStopTask();
917        clearPowerTask();
918
919        tablefeedback.dispose();
920
921    }
922
923    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusSlotMonitorDataModel.class);
924
925}