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