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 ArrayList<CbusSlotMonitorSession> _mainArray;
033
034    protected int _contype=0; //  pane console message type
035    protected String _context; // pane console text
036    private int cmndstat_fw =0; // command station firmware  TODO - get from node table
037
038    public static int CS_TIMEOUT = 2000; // command station timeout for estop and track messages
039    private static final int MAX_LINES = 5000;
040
041    // column order needs to match list in column tooltips
042    public static final int SESSION_ID_COLUMN = 0;
043    public static final int LOCO_ID_COLUMN = 1;
044    public static final int ESTOP_COLUMN = 2;
045    public static final int LOCO_ID_LONG_COLUMN = 3;
046    public static final int LOCO_COMMANDED_SPEED_COLUMN = 4;
047    public static final int LOCO_DIRECTION_COLUMN = 5;
048    public static final int FUNCTION_LIST = 6;
049    public static final int SPEED_STEP_COLUMN = 7;
050    public static final int LOCO_CONSIST_COLUMN = 8;
051    public static final int FLAGS_COLUMN = 9;
052
053    public static final int MAX_COLUMN = 10;
054
055    static final int[] CBUSSLOTMONINITIALCOLS = {0,1,2,4,5,6,9};
056
057    /**
058     * Create a New CbusSlotMonitorDataModel.
059     * Public access for user scripting.
060     * @param memo CAN System Connection to monitor.
061     */
062    public CbusSlotMonitorDataModel(CanSystemConnectionMemo memo) {
063
064        _mainArray = new ArrayList<>(0);
065        tablefeedback = new TextAreaFIFO(MAX_LINES);
066        tablefeedback.setEditable ( false );
067
068        // connect to the CanInterface
069        tc = memo.getTrafficController();
070        addTc(tc);
071        log.info("Starting {} CbusSlotMonitorDataModel", memo.getUserName());
072
073    }
074
075    protected TextAreaFIFO tablefeedback(){
076        return tablefeedback;
077    }
078
079    // order needs to match column list top of tabledatamodel
080    static 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                return Integer.class;
150            case LOCO_ID_LONG_COLUMN:
151                return Boolean.class;
152            case LOCO_DIRECTION_COLUMN:
153            case FUNCTION_LIST:
154            case FLAGS_COLUMN:
155            case SPEED_STEP_COLUMN:
156            case LOCO_COMMANDED_SPEED_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                updateMemory(_mainArray.get(row));
234                break;
235            case ESTOP_COLUMN:
236                int stopspeed=1;
237                if ( _mainArray.get(row).getDirection().equals(Bundle.getMessage("FWD") )
238                    && _mainArray.get(row).getSpeedSteps().equals("128") ) {
239                    stopspeed=129;
240                }
241                CanMessage m = new CanMessage(tc.getCanid());
242                m.setNumDataElements(3);
243                CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
244                m.setElement(0, CbusConstants.CBUS_DSPD);
245                m.setElement(1, _mainArray.get(row).getSessionId() );
246                m.setElement(2, stopspeed);
247                tc.sendCanMessage(m, null);
248                break;
249            case SPEED_STEP_COLUMN:
250                _mainArray.get(row).setSpeedSteps( (String) value );
251                updateGui(row,col);
252                break;
253            default:
254                log.warn("Failed to set value at column {}",col);
255                break;
256        }
257    }
258
259    private void updateGui(int row,int col) {
260        ThreadingUtil.runOnGUI( ()-> fireTableCellUpdated(row, col));
261    }
262
263    private boolean maintainLocoSpdMemory = false;
264
265    /**
266     * Set true to maintain a Memory Variable for the speed of each loco.
267     * Note this is an experimental method ( 5.5.5 ) and may be subject to change.
268     * <p>
269     * The Memory System Name is in the form e.g. IM12(S) or IM789(L)
270     * i.e. Internal Memory Loco 12, Short address.
271     * It may be easier to refer to this Memory in Jython scripts
272     * by giving it a User Name.
273     * <p>
274     * The Memory Value is the commanded Loco speed, 0-126.
275     * 0 includes a normal stop and e-stop.
276     * <p>
277     * The Value updates whenever a Loco speed command is heard on the
278     * connection hence not restricted to this JMRI instance.
279     * @since 5.5.5
280     * @param newVal true to enable updates, false to stop updates.
281     *               Default is false, no updates provided.
282     */
283    public void setMaintainLocoSpdMemory(boolean newVal) {
284        maintainLocoSpdMemory = newVal;
285    }
286
287    private void updateMemory(CbusSlotMonitorSession session){
288        if ( !maintainLocoSpdMemory || session==null ){
289            return;
290        }
291        MemoryManager memMgr = InstanceManager.getDefault(MemoryManager.class);
292        memMgr.provideMemory( memMgr.getSystemNamePrefix() + session.getLocoAddr() ).setValue(
293            jmri.util.StringUtil.getFirstIntFromString(session.getCommandedSpeed()));
294    }
295
296    private int createnewrow(int locoid, Boolean islong){
297
298        DccLocoAddress addr = new DccLocoAddress(locoid,islong );
299        CbusSlotMonitorSession newSession = new CbusSlotMonitorSession(addr);
300
301        _mainArray.add(newSession);
302        fireTableRowsInserted((getRowCount()-1), (getRowCount()-1));
303        return getRowCount()-1;
304    }
305
306    // returning the row number not the session
307    // so that any updates go through the table model
308    // and are updated in the GUI
309    private int provideTableRow( DccLocoAddress addr ) {
310        for (int i = 0; i < getRowCount(); i++) {
311            if ( addr.equals(_mainArray.get(i).getLocoAddr() ) )  {
312                return i;
313            }
314        }
315        return createnewrow(addr.getNumber(),addr.isLongAddress());
316    }
317
318    private int getrowfromsession(int sessionid){
319        for (int i = 0; i < getRowCount(); i++) {
320            if (sessionid==_mainArray.get(i).getSessionId() )  {
321                return i;
322            }
323        }
324        // no row so request session details from command station
325        CanMessage m = new CanMessage(tc.getCanid());
326        m.setNumDataElements(2);
327        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
328        m.setElement(0, CbusConstants.CBUS_QLOC);
329        m.setElement(1, sessionid);
330        tc.sendCanMessage(m, null);
331        // should receive a PLOC response with loco id etc.
332        return -1;
333    }
334
335    /**
336     * @param m outgoing CanMessage
337     */
338    @Override
339    public void message(CanMessage m) {
340        if ( m.extendedOrRtr() ) {
341            return;
342        }
343        int opc = CbusMessage.getOpcode(m);
344        // process is false as outgoing
345        switch (opc) {
346            case CbusConstants.CBUS_PLOC:
347                {
348                    int rcvdIntAddr = (m.getElement(2) & 0x3f) * 256 + m.getElement(3);
349                    boolean rcvdIsLong = (m.getElement(2) & 0xc0) != 0;
350                    processploc(m.getElement(1),new DccLocoAddress(rcvdIntAddr,rcvdIsLong),m.getElement(4),
351                            m.getElement(5),m.getElement(6),m.getElement(7));
352                    break;
353                }
354            case CbusConstants.CBUS_RLOC:
355                {
356                    int rcvdIntAddr = (m.getElement(1) & 0x3f) * 256 + m.getElement(2);
357                    boolean rcvdIsLong = (m.getElement(1) & 0xc0) != 0;
358                    processrloc(false,new DccLocoAddress(rcvdIntAddr,rcvdIsLong));
359                    break;
360                }
361            case CbusConstants.CBUS_DSPD:
362                processdspd(m.getElement(1),m.getElement(2));
363                break;
364            case CbusConstants.CBUS_DKEEP:
365                processdkeep(m.getElement(1));
366                break;
367            case CbusConstants.CBUS_KLOC:
368                processkloc(false,m.getElement(1));
369                break;
370            case CbusConstants.CBUS_GLOC:
371                int rcvdIntAddr = (m.getElement(1) & 0x3f) * 256 + m.getElement(2);
372                boolean rcvdIsLong = (m.getElement(1) & 0xc0) != 0;
373                processgloc(false,new DccLocoAddress(rcvdIntAddr,rcvdIsLong),m.getElement(3));
374                break;
375            case CbusConstants.CBUS_ERR:
376                processerr(false,m.getElement(1),m.getElement(2),m.getElement(3));
377                break;
378            case CbusConstants.CBUS_STMOD:
379                processstmod(false,m.getElement(1),m.getElement(2));
380                break;
381            case CbusConstants.CBUS_DFUN:
382                processdfun(m.getElement(1),m.getElement(2),m.getElement(3));
383                break;
384            case CbusConstants.CBUS_DFNON:
385                processdfnon(m.getElement(1),m.getElement(2),true);
386                break;
387            case CbusConstants.CBUS_DFNOF:
388                processdfnon(m.getElement(1),m.getElement(2),false); // same routine as DFNON
389                break;
390            case CbusConstants.CBUS_PCON:
391                processpcon(m.getElement(1),m.getElement(2));
392                break;
393            case CbusConstants.CBUS_KCON:
394                processpcon(m.getElement(1),0); // same routine as PCON
395                break;
396            case CbusConstants.CBUS_DFLG:
397                processdflg(m.getElement(1),m.getElement(2));
398                break;
399            case CbusConstants.CBUS_ESTOP:
400                processestop();
401                break;
402            case CbusConstants.CBUS_RTON:
403                processrton();
404                break;
405            case CbusConstants.CBUS_RTOF:
406                processrtof();
407                break;
408            case CbusConstants.CBUS_TON:
409                processton();
410                break;
411            case CbusConstants.CBUS_TOF:
412                processtof();
413                break;
414            default:
415                break;
416        }
417    }
418
419    /**
420     * @param m incoming cbus CanReply
421     */
422    @Override
423    public void reply(CanReply m) {
424        if ( m.extendedOrRtr() ) {
425            return;
426        }
427        int opc = CbusMessage.getOpcode(m);
428        int rcvdIntAddr;
429        boolean rcvdIsLong;
430        DccLocoAddress addr;
431        switch (opc) {
432            case CbusConstants.CBUS_STAT:
433                // todo more on this when finished tested v3 firmware with all opcs
434                // for now, if a stat opc is received then it's v4
435                // no stat received when < v4 Firmware
436                cmndstat_fw = 4;
437                break;
438            case CbusConstants.CBUS_PLOC:
439                rcvdIntAddr = (m.getElement(2) & 0x3f) * 256 + m.getElement(3);
440                rcvdIsLong = (m.getElement(2) & 0xc0) != 0;
441                addr = new DccLocoAddress(rcvdIntAddr,rcvdIsLong);
442                processploc(m.getElement(1),addr,m.getElement(4),
443                        m.getElement(5),m.getElement(6),m.getElement(7));
444                break;
445            case CbusConstants.CBUS_RLOC:
446                rcvdIntAddr = (m.getElement(1) & 0x3f) * 256 + m.getElement(2);
447                rcvdIsLong = (m.getElement(1) & 0xc0) != 0;
448                addr = new DccLocoAddress(rcvdIntAddr,rcvdIsLong);
449                processrloc(true,addr);
450                break;
451            case CbusConstants.CBUS_DSPD:
452                processdspd(m.getElement(1),m.getElement(2));
453                break;
454            case CbusConstants.CBUS_DKEEP:
455                processdkeep(m.getElement(1));
456                break;
457            case CbusConstants.CBUS_KLOC:
458                processkloc(true,m.getElement(1));
459                break;
460            case CbusConstants.CBUS_GLOC:
461                rcvdIntAddr = (m.getElement(1) & 0x3f) * 256 + m.getElement(2);
462                rcvdIsLong = (m.getElement(1) & 0xc0) != 0;
463                addr = new DccLocoAddress(rcvdIntAddr,rcvdIsLong);
464                processgloc(true,addr,m.getElement(3));
465                break;
466            case CbusConstants.CBUS_ERR:
467                processerr(true,m.getElement(1),m.getElement(2),m.getElement(3));
468                break;
469            case CbusConstants.CBUS_STMOD:
470                processstmod(true,m.getElement(1),m.getElement(2));
471                break;
472            case CbusConstants.CBUS_DFUN:
473                processdfun(m.getElement(1),m.getElement(2),m.getElement(3));
474                break;
475            case CbusConstants.CBUS_DFNON:
476                processdfnon(m.getElement(1),m.getElement(2),true);
477                break;
478            case CbusConstants.CBUS_DFNOF:
479                processdfnon(m.getElement(1),m.getElement(2),false);  // same routine as DFNON
480                break;
481            case CbusConstants.CBUS_PCON:
482                processpcon(m.getElement(1),m.getElement(2));
483                break;
484            case CbusConstants.CBUS_KCON:
485                processpcon(m.getElement(1),0); // same routine as PCON
486                break;
487            case CbusConstants.CBUS_DFLG:
488                processdflg(m.getElement(1),m.getElement(2));
489                break;
490            case CbusConstants.CBUS_ESTOP:
491                processestop();
492                break;
493            case CbusConstants.CBUS_RTON:
494                processrton();
495                break;
496            case CbusConstants.CBUS_RTOF:
497                processrtof();
498                break;
499            case CbusConstants.CBUS_TON:
500                processton();
501                break;
502            case CbusConstants.CBUS_TOF:
503                processtof();
504                break;
505            default:
506                break;
507        }
508    }
509
510    // ploc sent from a command station to a throttle
511    private void processploc( int session, DccLocoAddress addr,
512        int speeddir, int fa, int fb, int fc) {
513
514        int row = provideTableRow(addr);
515        setValueAt(session, row, SESSION_ID_COLUMN);
516        setValueAt(speeddir, row, LOCO_COMMANDED_SPEED_COLUMN);
517        processdfun( session, 1, fa);
518        processdfun( session, 2, fb);
519        processdfun( session, 3, fc);
520    }
521
522    // kloc sent from throttle to command station to release loco, which will continue at current speed
523    private void processkloc(boolean messagein, int session) {
524        int row=getrowfromsession(session);
525        String messagedir;
526        if (messagein){ // external throttle
527            messagedir = Bundle.getMessage("CBUS_IN_CAB");
528        } else { // jmri throttle
529            messagedir = Bundle.getMessage("CBUS_OUT_CMD");
530        }
531        log.debug("direction {} kloc {}",messagedir,Bundle.getMessage("CNFO_KLOC",session));
532        if ( row > -1 ) {
533            setValueAt(0, row, SESSION_ID_COLUMN); // Session restored by sending QLOC if v4 firmware
534
535            // version 4 fw maintains version number, so to check this request session details from command station
536            // if this is sent with the v3 firmware then a popup error comes up from cbus throttlemanager when
537            // errStr is populated in the switch error clauses in canreply.
538            // check if version 4
539            if ( ( cmndstat_fw > 3 ) && ( !"0".startsWith(_mainArray.get(row).getCommandedSpeed()) )) {
540                log.debug("send qloc {} {}",Bundle.getMessage("CBUS_OUT_CMD"),Bundle.getMessage("QuerySession8a",session));
541                CanMessage m = new CanMessage(tc.getCanid());
542                m.setNumDataElements(2);
543                CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
544                m.setElement(0, CbusConstants.CBUS_QLOC);
545                m.setElement(1, session);
546                tc.sendCanMessage(m, null);
547            }
548        }
549    }
550
551    // rloc sent from throttle to command station to get loco
552    private void processrloc(boolean messagein, DccLocoAddress addr ) {
553        int row = provideTableRow(addr);
554        log.debug("{} new table row {}", messagein,row);
555    }
556
557    // gloc sent from throttle to command station to get loco
558    private void processgloc(boolean messagein, DccLocoAddress addr, int flags) {
559        int row = provideTableRow(addr);
560        log.debug ("processgloc row {}",row);
561        StringBuilder flagstring = new StringBuilder();
562        if (messagein){ // external throttle
563            flagstring.append(Bundle.getMessage("CBUS_IN_CAB"));
564        } else { // jmri throttle
565            flagstring.append(Bundle.getMessage("CBUS_OUT_CMD"));
566        }
567
568        boolean stealmode = ((flags ) & 1) != 0;
569        boolean sharemode = ((flags >> 1 ) & 1) != 0;
570        if (stealmode){
571            flagstring.append(Bundle.getMessage("CNFO_GLOC_ST"));
572        }
573        else if (sharemode){
574            flagstring.append(Bundle.getMessage("CNFO_GLOC_SH"));
575        }
576        else {
577            flagstring.append(Bundle.getMessage("CNFO_GLOC"));
578        }
579        flagstring.append(addr);
580        addToLog(1,flagstring.toString());
581    }
582
583    // stmod sent from throttle to cmmnd station if speed steps not 128 / set service mode / sound mode
584    private void processstmod(boolean messagein, int session, int flags) {
585        int row=getrowfromsession(session);
586        if ( row > -1 ) {
587            String messagedir;
588            if (messagein){ // external throttle
589                messagedir=( Bundle.getMessage("CBUS_IN_CAB"));
590            } else { // jmri throttle
591                messagedir=( Bundle.getMessage("CBUS_OUT_CMD"));
592            }
593
594            boolean sm0 = ((flags ) & 1) != 0;
595            boolean sm1 = ((flags >> 1 ) & 1) != 0;
596            boolean servicemode = ((flags >> 2 ) & 1) != 0;
597            boolean soundmode = ((flags >> 3 ) & 1) != 0;
598
599            String speedstep="";
600            if ((!sm0) && (!sm1)){
601                speedstep="128";
602            }
603            else if ((!sm0) && (sm1)){
604                speedstep="14";
605            }
606            else if ((sm0) && (!sm1)){
607                speedstep="28I";
608            }
609            else if ((sm0) && (sm1)){
610                speedstep="28";
611            }
612            log.debug("processstmod {} {}",messagedir,Bundle.getMessage("CNFO_STMOD",session,speedstep,servicemode,soundmode));
613            setValueAt(speedstep, row, SPEED_STEP_COLUMN);
614        }
615    }
616
617    // DKEEP sent as keepalive from throttle to command station
618    private void processdkeep(int session) {
619        int row=getrowfromsession(session);
620        if ( row < 0 ) {
621            log.debug("Requesting loco details for session {}.",session );
622        }
623    }
624
625    // DSPD sent from throttle to command station , speed / direction
626    private void processdspd( int session, int speeddir) {
627        int row=getrowfromsession(session);
628        if ( row > -1 ) {
629            setValueAt(speeddir, row, LOCO_COMMANDED_SPEED_COLUMN);
630        }
631    }
632
633    // DFLG sent from throttle to command station to notify engine change in flags
634    private void processdflg( int session, int flags) {
635        int row=getrowfromsession(session);
636        if ( row>-1 ) {
637            _mainArray.get(row).setFlags(flags);
638            updateGui(row,SPEED_STEP_COLUMN);
639            updateGui(row,FLAGS_COLUMN);
640        }
641    }
642
643    // DFNON Sent by a cab to turn on a specific loco function, alternative method to DFUN
644    // also used to process function responses from DFNOF
645    private void processdfnon( int session, int function, boolean trueorfalse) {
646        int row=getrowfromsession(session);
647        if ( row>-1 && function>-1 && function<29 ) {
648            _mainArray.get(row).setFunction(function,trueorfalse);
649            updateGui(row,FUNCTION_LIST);
650        }
651    }
652
653    // DFUN Sent by a cab to trigger loco function
654    // also used to process function responses from PLOC
655    private void processdfun( int session, int range, int functionbyte) {
656        int row=getrowfromsession(session);
657        if ( row > -1 ) {
658            switch (range) {
659                case 1:
660                    _mainArray.get(row).setFunction(0, ((functionbyte & CbusConstants.CBUS_F0) == CbusConstants.CBUS_F0));
661                    _mainArray.get(row).setFunction(1, ((functionbyte & CbusConstants.CBUS_F1) == CbusConstants.CBUS_F1));
662                    _mainArray.get(row).setFunction(2, ((functionbyte & CbusConstants.CBUS_F2) == CbusConstants.CBUS_F2));
663                    _mainArray.get(row).setFunction(3, ((functionbyte & CbusConstants.CBUS_F3) == CbusConstants.CBUS_F3));
664                    _mainArray.get(row).setFunction(4, ((functionbyte & CbusConstants.CBUS_F4) == CbusConstants.CBUS_F4));
665                    break;
666                case 2:
667                    _mainArray.get(row).setFunction(5, ((functionbyte & CbusConstants.CBUS_F5) == CbusConstants.CBUS_F5));
668                    _mainArray.get(row).setFunction(6, ((functionbyte & CbusConstants.CBUS_F6) == CbusConstants.CBUS_F6));
669                    _mainArray.get(row).setFunction(7, ((functionbyte & CbusConstants.CBUS_F7) == CbusConstants.CBUS_F7));
670                    _mainArray.get(row).setFunction(8, ((functionbyte & CbusConstants.CBUS_F8) == CbusConstants.CBUS_F8));
671                    break;
672                case 3:
673                    _mainArray.get(row).setFunction(9, ((functionbyte & CbusConstants.CBUS_F9) == CbusConstants.CBUS_F9));
674                    _mainArray.get(row).setFunction(10, ((functionbyte & CbusConstants.CBUS_F10) == CbusConstants.CBUS_F10));
675                    _mainArray.get(row).setFunction(11, ((functionbyte & CbusConstants.CBUS_F11) == CbusConstants.CBUS_F11));
676                    _mainArray.get(row).setFunction(12, ((functionbyte & CbusConstants.CBUS_F12) == CbusConstants.CBUS_F12));
677                    break;
678                case 4:
679                    _mainArray.get(row).setFunction(13, ((functionbyte & CbusConstants.CBUS_F13) == CbusConstants.CBUS_F13));
680                    _mainArray.get(row).setFunction(14, ((functionbyte & CbusConstants.CBUS_F14) == CbusConstants.CBUS_F14));
681                    _mainArray.get(row).setFunction(15, ((functionbyte & CbusConstants.CBUS_F15) == CbusConstants.CBUS_F15));
682                    _mainArray.get(row).setFunction(16, ((functionbyte & CbusConstants.CBUS_F16) == CbusConstants.CBUS_F16));
683                    _mainArray.get(row).setFunction(17, ((functionbyte & CbusConstants.CBUS_F17) == CbusConstants.CBUS_F17));
684                    _mainArray.get(row).setFunction(18, ((functionbyte & CbusConstants.CBUS_F18) == CbusConstants.CBUS_F18));
685                    _mainArray.get(row).setFunction(19, ((functionbyte & CbusConstants.CBUS_F19) == CbusConstants.CBUS_F19));
686                    _mainArray.get(row).setFunction(20, ((functionbyte & CbusConstants.CBUS_F20) == CbusConstants.CBUS_F20));
687                    break;
688                case 5:
689                    _mainArray.get(row).setFunction(21, ((functionbyte & CbusConstants.CBUS_F21) == CbusConstants.CBUS_F21));
690                    _mainArray.get(row).setFunction(22, ((functionbyte & CbusConstants.CBUS_F22) == CbusConstants.CBUS_F22));
691                    _mainArray.get(row).setFunction(23, ((functionbyte & CbusConstants.CBUS_F23) == CbusConstants.CBUS_F23));
692                    _mainArray.get(row).setFunction(24, ((functionbyte & CbusConstants.CBUS_F24) == CbusConstants.CBUS_F24));
693                    _mainArray.get(row).setFunction(25, ((functionbyte & CbusConstants.CBUS_F25) == CbusConstants.CBUS_F25));
694                    _mainArray.get(row).setFunction(26, ((functionbyte & CbusConstants.CBUS_F26) == CbusConstants.CBUS_F26));
695                    _mainArray.get(row).setFunction(27, ((functionbyte & CbusConstants.CBUS_F27) == CbusConstants.CBUS_F27));
696                    _mainArray.get(row).setFunction(28, ((functionbyte & CbusConstants.CBUS_F28) == CbusConstants.CBUS_F28));
697                    break;
698                default:
699                    break;
700            }
701            updateGui(row,FUNCTION_LIST);
702        }
703    }
704
705    // ERR sent by command station
706    private void processerr(boolean messagein, int one, int two, int errnum) {
707        int rcvdIntAddr = (one & 0x3f) * 256 + two;
708
709        StringBuilder buf = new StringBuilder();
710        if (messagein){ // external throttle
711            buf.append( Bundle.getMessage("CBUS_CMND_BR"));
712        } else { // jmri throttle
713            buf.append( Bundle.getMessage("CBUS_OUT_CMD"));
714        }
715
716        switch (errnum) {
717            case 1:
718                buf.append(Bundle.getMessage("ERR_LOCO_STACK_FULL"));
719                buf.append(rcvdIntAddr);
720                break;
721            case 2:
722                buf.append(Bundle.getMessage("ERR_LOCO_ADDRESS_TAKEN",rcvdIntAddr));
723                break;
724            case 3:
725                buf.append(Bundle.getMessage("ERR_SESSION_NOT_PRESENT",one));
726                break;
727            case 4:
728                buf.append(Bundle.getMessage("ERR_CONSIST_EMPTY"));
729                buf.append(one);
730                break;
731            case 5:
732                buf.append(Bundle.getMessage("ERR_LOCO_NOT_FOUND"));
733                buf.append(one);
734                break;
735            case 6:
736                buf.append(Bundle.getMessage("ERR_CAN_BUS_ERROR"));
737                break;
738            case 7:
739                buf.append(Bundle.getMessage("ERR_INVALID_REQUEST"));
740                buf.append(rcvdIntAddr);
741                break;
742            case 8:
743                buf.append(Bundle.getMessage("ERR_SESSION_CANCELLED",one));
744                // cancel session number in table
745                int row = getrowfromsession(one);
746                if ( row > -1 ) {
747                    setValueAt(0, row, SESSION_ID_COLUMN);
748                }
749                break;
750            default:
751                break;
752        }
753        _context = buf.toString();
754        addToLog(1,_context);
755    }
756
757    // PCON sent by throttle to add to consist
758    // also used to process remove from consist KCON
759    private void processpcon( int session, int consist){
760        log.debug("processing pcon");
761        int row=getrowfromsession(session);
762        if ( row>-1 ) {
763
764            int consistaddr = (consist & 0x7f);
765            setValueAt(consistaddr, row, LOCO_CONSIST_COLUMN);
766
767            StringBuilder buf = new StringBuilder();
768            buf.append( Bundle.getMessage("CNFO_PCON",session,consistaddr));
769            if ((consist & 0x80) == 0x80){
770                buf.append( Bundle.getMessage("FWD"));
771            } else {
772                buf.append( Bundle.getMessage("REV"));
773            }
774            addToLog(1,buf.toString() );
775        }
776    }
777
778    private void processestop(){
779        addToLog(1,"Command station acknowledges estop");
780        clearEStopTask();
781    }
782
783    private void processrton(){
784        setPowerTask();
785    }
786
787    private void processrtof(){
788        setPowerTask();
789    }
790
791    private void processton(){
792        clearPowerTask();
793        log.debug("Track on confirmed from command station.");
794    }
795
796    private void processtof(){
797        clearPowerTask();
798        log.debug("Track off confirmed from command station.");
799    }
800
801    public void sendcbusestop(){
802        log.info("Sending Command Station e-stop");
803        CanMessage m = new CanMessage(tc.getCanid());
804        m.setNumDataElements(1);
805        CbusMessage.setPri(m, CbusConstants.DEFAULT_DYNAMIC_PRIORITY * 4 + CbusConstants.DEFAULT_MINOR_PRIORITY);
806        m.setElement(0, CbusConstants.CBUS_RESTP);
807        tc.sendCanMessage(m, null);
808
809        // start a timer to monitor if timeout, ie if command station doesn't respond
810        setEstopTask();
811    }
812
813    private transient TimerTask eStopTask;
814
815    private void clearEStopTask() {
816        if (eStopTask != null ) {
817            eStopTask.cancel();
818            eStopTask = null;
819        }
820    }
821
822    private void setEstopTask() {
823        eStopTask = new TimerTask() {
824            @Override
825            public void run() {
826                eStopTask = null;
827                addToLog(1,("Send Estop No Response received from command station."));
828                log.info("Send Estop No Response received from command station.");
829            }
830        };
831        TimerUtil.schedule(eStopTask, ( CS_TIMEOUT ) );
832    }
833
834    private transient TimerTask powerTask;
835
836    private void clearPowerTask() {
837        if (powerTask != null ) {
838            powerTask.cancel();
839            powerTask = null;
840        }
841    }
842
843    private void setPowerTask() {
844        powerTask = new TimerTask() {
845            @Override
846            public void run() {
847                powerTask = null;
848                addToLog(1,("Track Power - No Response received from command station."));
849                log.info("Track Power - No Response received from command station.");
850            }
851        };
852        TimerUtil.schedule(powerTask, ( CS_TIMEOUT ) );
853    }
854
855    /**
856     * Add to Slot Monitor Console Log
857     * @param cbuserror int
858     * @param cbustext String console message
859     */
860    public void addToLog(int cbuserror, String cbustext){
861        ThreadingUtil.runOnGUI( ()-> tablefeedback.append( System.lineSeparator()+cbustext));
862    }
863
864    /**
865     * disconnect from the CBUS
866     */
867    @Override
868    public void dispose() {
869        removeTc(tc);
870
871        // stop timers if running
872        clearEStopTask();
873        clearPowerTask();
874
875        tablefeedback.dispose();
876
877    }
878
879    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusSlotMonitorDataModel.class);
880
881}