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