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}