001package jmri.jmrix.dcc4pc; 002 003import java.util.ArrayList; 004import java.util.Arrays; 005import java.util.Enumeration; 006import java.util.HashMap; 007import java.util.List; 008import java.util.concurrent.ConcurrentHashMap; 009import javax.annotation.Nonnull; 010import jmri.JmriException; 011import jmri.Sensor; 012 013/** 014 * Implement SensorManager for Dcc4Pc systems. The Manager handles all the state 015 * changes. 016 * <p> 017 * System names are "DSnn:yy", where D is the user configurable system prefix, 018 * nn is the board id and yy is the port on that board. 019 * 020 * @author Kevin Dickerson Copyright (C) 2009 021 */ 022public class Dcc4PcSensorManager extends jmri.managers.AbstractSensorManager 023 implements Dcc4PcListener { 024 025 public Dcc4PcSensorManager(Dcc4PcTrafficController tc, Dcc4PcSystemConnectionMemo memo) { 026 super(memo); 027 this.tc = tc; 028 this.reportManager = (Dcc4PcReporterManager) memo.get(jmri.ReporterManager.class); 029 jmri.InstanceManager.store(Dcc4PcSensorManager.this, Dcc4PcSensorManager.class); 030 this.boardManager = new Dcc4PcBoardManager(tc, this); 031 // Finally, create and register a shutdown task to ensure clean exit 032 this.pollShutDownTask = this::stopPolling; 033 startPolling(); 034 } 035 036 Dcc4PcReporterManager reportManager; 037 Runnable pollShutDownTask; 038 Dcc4PcBoardManager boardManager; 039 040 Dcc4PcTrafficController tc; 041 042 @Override 043 public Dcc4PcSensor getSensor(@Nonnull String name) { 044 return (Dcc4PcSensor) super.getSensor(name); 045 046 } 047 048 /** 049 * {@inheritDoc} 050 */ 051 @Override 052 @Nonnull 053 public Dcc4PcSystemConnectionMemo getMemo() { 054 return (Dcc4PcSystemConnectionMemo) memo; 055 } 056 057 /** 058 * {@inheritDoc} 059 */ 060 @Override 061 @Nonnull 062 protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException { 063 Sensor s = new Dcc4PcSensor(systemName, userName); 064 s.setUserName(userName); 065 extractBoardID(systemName); 066 return s; 067 } 068 069 /** 070 * This extracts the board id out from the system name. 071 * @param systemName including system prefix and type letter. 072 */ 073 void extractBoardID(String systemName) { 074 if (systemName.contains(":")) { 075 int indexOfSplit = systemName.indexOf(":"); 076 systemName = systemName.substring(0, indexOfSplit); 077 indexOfSplit = getSystemPrefix().length() + 1; // +1 includes the typeletter which is a char 078 systemName = systemName.substring(indexOfSplit); 079 int boardNo; 080 try { 081 boardNo = Integer.parseInt(systemName); 082 } catch (NumberFormatException ex) { 083 log.error("Unable to find the board address from system name {}", systemName); 084 return; 085 } 086 addBoard(boardNo); 087 } 088 } 089 090 @Override 091 public boolean allowMultipleAdditions(@Nonnull String systemName) { 092 return true; 093 } 094 095 // we want the system name to be in the format of board:input 096 @Override 097 @Nonnull 098 public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException { 099 String iName; 100 if (curAddress.contains(":")) { 101 int board = 0; 102 int channel; 103 // Address format passed is in the form of board:channel or T:turnout address 104 int seperator = curAddress.indexOf(":"); 105 try { 106 board = Integer.parseInt(curAddress.substring(0, seperator)); 107 } catch (NumberFormatException ex) { 108 throw new JmriException("Unable to convert "+curAddress+" into the cab and channel format of nn:xx"); 109 } 110 111 try { 112 channel = Integer.parseInt(curAddress.substring(seperator + 1)); 113 if ((channel > 16) || (channel < 1)) { 114 throw new JmriException("In Address "+curAddress+" Channel number should be in the range of 1 to 16"); 115 } 116 } catch (NumberFormatException ex) { 117 throw new JmriException("Unable to convert "+curAddress+" into the cab and channel format of nn:xx"); 118 } 119 iName = curAddress; 120 addBoard(board); 121 } else { 122 throw new JmriException("Unable to convert "+curAddress+" into the cab and channel format of nn:xx"); 123 } 124 return prefix + typeLetter() + iName; 125 } 126 127 public void notifyReply(Dcc4PcReply m) { 128 // is this a list of sensors? 129 } 130 131 public void notifyMessage(Dcc4PcMessage m) { 132 // messages are ignored 133 } 134 135 Thread pollThread; 136 boolean stopPolling = true; 137 138 protected void stopPolling() { 139 synchronized (this) { 140 stopPolling = true; 141 } 142 if (pollThread != null) { 143 // we want to wait for the polling thread to finish what it is currently working on. 144 try { 145 pollThread.join(); 146 } catch (InterruptedException e) { 147 // Don't need to worry 148 } 149 } 150 } 151 152 final protected void startPolling() { 153 if (stopPolling && pollThread != null) { 154 pollThread = null; 155 } 156 stopPolling = false; 157 158 if (pollThread == null) { 159 pollThread = new Thread(this::pollManager, "DCC4PC Sensor Poll"); 160 pollThread.start(); 161 } 162 } 163 164 void addBoard(int newBoard) { 165 boardManager.addBoard(newBoard); 166 } 167 168 @Override 169 public void reply(Dcc4PcReply r) { 170 if (log.isDebugEnabled()) { 171 log.debug("Reply details sm: {}", r.toHexString()); 172 } 173 174 if (r.getNumDataElements() == 0 && r.getElement(0) == 0x00) { 175 //Simple acknowledgement reply, no further action required 176 return; 177 } 178 if (r.getBoard() == -1) { 179 log.debug("Message is not for a detection board so ignore"); 180 return; 181 } 182 if (r.isError()) { 183 log.debug("Reply is in error {}", r.toHexString()); 184 synchronized (this) { 185 awaitingReply = false; 186 this.notify(); 187 } 188 } else if (!r.isUnsolicited()) { 189 synchronized (this) { 190 awaitingReply = false; 191 this.notify(); 192 } 193 if (log.isDebugEnabled()) { 194 log.debug("Get Data inputs {}", r.toHexString()); 195 } 196 class ProcessPacket implements Runnable { 197 198 Dcc4PcReply reply; 199 200 ProcessPacket(Dcc4PcReply r) { 201 reply = r; 202 } 203 204 @Override 205 public void run() { 206 ActiveBoard curBoard = activeBoards.get(r.getBoard()); 207 if (curBoard != null) { 208 curBoard.processInputPacket(reply); 209 } else { 210 log.error("Board disappeared from system {}", r.getBoard()); 211 } 212 } 213 } 214 if (r.getBoard() > -1) { 215 Thread thr = new Thread(new ProcessPacket(r), "Dcc4PCSensor Process Packet for " + r.getBoard()); 216 try { 217 thr.start(); 218 } catch (java.lang.IllegalThreadStateException ex) { 219 log.error("Exception {} {}", thr.getName(), ex.getMessage()); 220 } 221 } else { 222 log.error("Do not know who this board message is for"); 223 } 224 225 } 226 } 227 228 // This possibly needs to be handled better 229 void getInputState(int[] longArray, int board) { 230 String sensorPrefix = getSystemPrefix() + typeLetter() + board + ":"; 231 String reporterPrefix = getSystemPrefix() + "R" + board + ":"; 232 int inputNo = 1; //Maximum number of inputs is 16, but some might not be enabled, so need to handle this some how at a later date 233 for (int i = 0; i < 4; i++) { 234 Dcc4PcSensor s; 235 Dcc4PcReporter r; 236 int state = getInputState(longArray[i], inputNo); 237 s = getSensor(sensorPrefix + (inputNo)); 238 if (s != null) { 239 s.setOwnState(state); 240 } 241 r = ((Dcc4PcReporter) reportManager.getReporter(reporterPrefix + (inputNo))); 242 if (r != null) { 243 r.setRailComState(state); 244 } 245 inputNo++; 246 247 state = getInputState(longArray[i], inputNo); 248 s = getSensor(sensorPrefix + (inputNo)); 249 if (s != null) { 250 s.setOwnState(state); 251 } 252 r = ((Dcc4PcReporter) reportManager.getReporter(reporterPrefix + (inputNo))); 253 if (r != null) { 254 r.setRailComState(state); 255 } 256 inputNo++; 257 258 state = getInputState(longArray[i], inputNo); 259 s = getSensor(sensorPrefix + (inputNo)); 260 if (s != null) { 261 s.setOwnState(state); 262 } 263 r = ((Dcc4PcReporter) reportManager.getReporter(reporterPrefix + (inputNo))); 264 if (r != null) { 265 r.setRailComState(state); 266 } 267 inputNo++; 268 269 state = getInputState(longArray[i], inputNo); 270 s = getSensor(sensorPrefix + (inputNo)); 271 if (s != null) { 272 s.setOwnState(state); 273 } 274 r = ((Dcc4PcReporter) reportManager.getReporter(reporterPrefix + (inputNo))); 275 if (r != null) { 276 r.setRailComState(state); 277 } 278 inputNo++; 279 } 280 } 281 282 int getInputState(int value, int input) { 283 int lastbit = 7; 284 switch (input) { 285 case 5: 286 case 9: 287 case 13: 288 case 1: 289 lastbit = 1; 290 break; 291 case 6: 292 case 10: 293 case 14: 294 case 2: 295 lastbit = 3; 296 break; 297 case 7: 298 case 11: 299 case 15: 300 case 3: 301 lastbit = 5; 302 break; 303 case 8: 304 case 12: 305 case 16: 306 case 4: 307 lastbit = 7; 308 break; 309 default: 310 break; 311 } 312 int tempValue = value << (31 - lastbit); 313 switch (tempValue >>> (31 - lastbit + (lastbit - 1))) { 314 case 0: 315 return Sensor.INACTIVE; 316 case 1: 317 return Sensor.ACTIVE; 318 case 2: 319 return Dcc4PcSensor.ORIENTA; //Occupied RailCom Orientation A 320 case 3: 321 return Dcc4PcSensor.ORIENTB; //Occupied RailCom Orientation B 322 default: 323 return Sensor.UNKNOWN; 324 } 325 } 326 327 public static String decodeInputState(int state) { 328 String rtr; 329 switch (state) { 330 case Sensor.INACTIVE: 331 rtr = "UnOccupied"; 332 break; 333 case Sensor.ACTIVE: 334 rtr = "Occupied No RailCom Data"; 335 break; 336 case Dcc4PcSensor.ORIENTA: 337 rtr = "Occupied RailCom Orientation A"; 338 break; 339 case Dcc4PcSensor.ORIENTB: 340 rtr = "Occupied RailCom Orientation B"; 341 break; 342 default: 343 rtr = "Unknown"; 344 } 345 return rtr; 346 } 347 348 public final static int NO_ADDRESS = 0x00; 349 public final static int SHORT_ADDRESS = 0x02; 350 public final static int LONG_ADDRESS = 0x04; 351 public final static int CONSIST_ADDRESS = 0x08; 352 353 /** 354 * Determine if the Railcom data is duplicated. 355 * If it is then this instructs the Reporter to move things about. 356 * @param value Railcom data 357 * @param seq message sequence number 358 * @param rc Reporter to request action(s) from 359 * @return value calculated locally 360 */ 361 int decodeDuplicatePacket(int value, int seq, Dcc4PcReporter rc) { 362 int lastbit = 7; 363 //probably a better way of doing this.. 364 while (seq >= 4) { 365 seq = seq - 4; 366 } 367 368 switch (seq) { 369 case 0: 370 lastbit = 1; 371 break; 372 case 1: 373 lastbit = 3; 374 break; 375 case 2: 376 lastbit = 5; 377 break; 378 case 3: 379 lastbit = 7; 380 break; 381 default: 382 break; 383 } 384 int tempValue = value << (31 - lastbit); 385 tempValue = (tempValue >>> (31 - lastbit + (lastbit - 1))); 386 if (tempValue != 0) { 387 rc.duplicatePacket(tempValue); 388 } 389 return tempValue; 390 } 391 392 private final int shortCycleInterval = 1; // Time to wait between sending out poll messages 393 private final int pollTimeout = 600; // in case of lost response 394 private boolean awaitingReply = false; 395 396 void pollManager() { 397 for (int boardAddress : activeBoards.keySet()) { 398 Dcc4PcMessage m = Dcc4PcMessage.resetBoardData(boardAddress); 399 m.setTimeout(100); 400 tc.sendDcc4PcMessage(m, null); 401 } 402 while (!stopPolling) { 403 if (activeBoards.isEmpty()) { 404 //If we have no boards to poll then wait a second. 405 try { 406 Thread.sleep(1000); 407 } catch (java.lang.InterruptedException e) { 408 } 409 } else { 410 for (int boardAddress : activeBoards.keySet()) { 411 if (!activeBoards.get(boardAddress).doNotPoll()) { 412 log.debug("Poll board {}", boardAddress); 413 Dcc4PcMessage m = Dcc4PcMessage.pollBoard(boardAddress); 414 log.debug("queueing poll request for board {}", boardAddress); 415 tc.sendDcc4PcMessage(m, this); 416 synchronized (this) { 417 awaitingReply = true; 418 try { 419 wait(pollTimeout); 420 } catch (InterruptedException e) { 421 Thread.currentThread().interrupt(); // retain if needed later 422 } 423 } 424 int delay = shortCycleInterval; 425 synchronized (this) { 426 if (awaitingReply) { 427 log.warn("timeout awaiting poll response for board {}", boardAddress); 428 delay = pollTimeout; 429 } 430 try { 431 wait(delay); 432 } catch (InterruptedException e) { 433 Thread.currentThread().interrupt(); // retain if needed later 434 } finally { 435 /*awaitingDelay = false;*/ 436 } 437 } 438 } else { 439 log.debug("Board set to Do Not Poll {}", boardAddress); 440 } 441 synchronized (this) { 442 if (stopPolling) { 443 log.debug("Polling stopped {}", stopPolling); 444 Thread.currentThread().interrupt(); 445 return; 446 } 447 } 448 } 449 } 450 } 451 } 452 453 ConcurrentHashMap<Integer, ActiveBoard> activeBoards = new ConcurrentHashMap<>(5); 454 455 /** 456 * Generate all the sensor and reporter details based upon 457 * the reply message from the board. 458 * 459 * @param r Reply that we build the information from 460 */ 461 protected void createSensorsFromReply(Dcc4PcReply r) { 462 int boardAddress = r.getBoard(); 463 log.debug("createSensorsFromReply: Get enabled inputs {}", r.toHexString()); 464 465 String sensorPrefix = getSystemPrefix() + typeLetter() + boardAddress + ":"; 466 String reporterPrefix = getSystemPrefix() + "R" + boardAddress + ":"; 467 468 int x = 1; 469 for (int i = 0; i < r.getNumDataElements(); i++) { 470 471 for (int j = 0; j < 8; j++) { 472 Dcc4PcSensor s = (Dcc4PcSensor) createNewSensor(sensorPrefix + (j + x), null); 473 register(s); 474 s.setInput(j + x); 475 activeBoards.get(boardAddress).addSensor(j + x, s); 476 if ((r.getElement(i) & 0x01) == 0x01) { 477 s.setEnabled(true); 478 } 479 Dcc4PcReporter report = (Dcc4PcReporter) reportManager.createNewReporter(reporterPrefix + (j + x), null); 480 activeBoards.get(boardAddress).addReporter(j + x, report); 481 482 } 483 x = x + 8; 484 } 485 activeBoards.get(boardAddress).setDoNotPoll(false); 486 log.debug(" created {} sensors", x); 487 } 488 489 @Override 490 public void handleTimeout(Dcc4PcMessage m) { 491 log.debug("timeout received to our last message {}", m); 492 if (!stopPolling) { 493 synchronized (this) { 494 awaitingReply = false; 495 this.notify(); 496 } 497 } 498 } 499 500 @Override 501 public void message(Dcc4PcMessage m) { 502 503 } 504 505 public void changeBoardAddress(int oldAddress, int newAddress) { 506 // Block polling on this board 507 ActiveBoard board = activeBoards.get(oldAddress); 508 board.setDoNotPoll(true); 509 Dcc4PcMessage m = new jmri.jmrix.dcc4pc.Dcc4PcMessage(new byte[]{(byte) 0x0b, (byte) oldAddress, (byte) 0x03, (byte) newAddress}); 510 tc.sendDcc4PcMessage(m, null); 511 // Need to stop polling otherwise we get a concurrent modification exception 512 stopPolling(); 513 activeBoards.remove(oldAddress); 514 activeBoards.put(newAddress, board); 515 board.setAddress(newAddress); 516 startPolling(); 517 String sensorPrefix = getSystemPrefix() + typeLetter() + newAddress + ":"; 518 String reporterPrefix = getSystemPrefix() + "R" + newAddress + ":"; 519 // We create a new set of sensors and reporters, but leave the old ones. 520 for (int i = 1; i < board.getNumEnabledSensors() + 1; i++) { 521 board.getSensorAtIndex(i).setEnabled(false); 522 int input = board.getSensorAtIndex(i).getInput(); 523 Dcc4PcSensor s = (Dcc4PcSensor) createNewSensor(sensorPrefix + (input), null); 524 register(s); 525 s.setInput(input); 526 s.setEnabled(true); 527 board.addSensor(input, s); 528 Dcc4PcReporter report = (Dcc4PcReporter) reportManager.createNewReporter(reporterPrefix + (input), null); 529 board.addReporter(input, report); 530 } 531 // Need to update the sensors used. 532 board.setDoNotPoll(false); 533 } 534 535 class ActiveBoard { 536 537 ActiveBoard(int address, String version, int inputs, int encoding) { 538 this.version = version; 539 this.inputs = inputs; 540 this.encoding = encoding; 541 this.address = address; 542 } 543 544 void setAddress(int address) { 545 this.address = address; 546 } 547 548 int inputs; 549 int encoding; 550 String version; 551 String description; 552 int address; 553 554 int failedRequests = 0; 555 556 void addFailedRequests() { 557 failedRequests++; 558 } 559 560 void clearFailedRequests() { 561 failedRequests = 0; 562 } 563 564 boolean doNotPoll = true; 565 566 void setDoNotPoll(boolean poll) { 567 if (!poll) { 568 Dcc4PcMessage m = Dcc4PcMessage.resetBoardData(address); 569 m.setTimeout(100); 570 tc.sendDcc4PcMessage(m, null); 571 } 572 doNotPoll = poll; 573 } 574 575 boolean doNotPoll() { 576 return doNotPoll; 577 } 578 579 HashMap<Integer, Dcc4PcSensor> inputPorts = new HashMap<>(16); 580 581 void addSensor(int port, Dcc4PcSensor sensor) { 582 inputPorts.put(port, sensor); 583 } 584 585 String getEncodingAsString() { 586 if ((encoding & 0x01) == 0x01) { 587 return "Supports Cooked RailCom Encoding"; 588 589 } else { 590 return "Supports Raw RailCom Encoding"; 591 } 592 } 593 594 void setDescription(String description) { 595 this.description = description; 596 } 597 598 Dcc4PcSensor getSensorAtIndex(int i) { 599 if (!inputPorts.containsKey(i)) { 600 return null; 601 } 602 return inputPorts.get(i); 603 } 604 605 int getNumEnabledSensors() { 606 return inputPorts.size(); 607 } 608 609 HashMap<Integer, Dcc4PcReporter> inputReportersPorts = new HashMap<>(16); 610 611 void addReporter(int port, Dcc4PcReporter reporter) { 612 inputReportersPorts.put(port, reporter); 613 } 614 615 Dcc4PcReporter getReporterAtIndex(int i) { 616 if (!inputReportersPorts.containsKey(i)) { 617 return null; 618 } 619 return inputReportersPorts.get(i); 620 } 621 622 synchronized void processInputPacket(Dcc4PcReply r) { 623 if (log.isDebugEnabled()) { 624 log.debug("==== Process Packet ===="); 625 log.debug("hex = {}", r.toHexString()); 626 } 627 int packetTypeCmd = 0x00; 628 int currentByteLocation = 0; 629 while (currentByteLocation < r.getNumDataElements()) { 630 log.debug("--- Start {} ---", currentByteLocation); 631 int oldstart = currentByteLocation; 632 if ((r.getElement(currentByteLocation) & 0x80) == 0x80) { 633 log.debug("Error at head"); 634 packetTypeCmd = 0x03; 635 ++currentByteLocation; 636 } else if ((r.getElement(currentByteLocation) & 0x40) == 0x40) { 637 log.debug("Correct Type 2 packet"); 638 currentByteLocation = processPacket(r, 0x02, packetTypeCmd, currentByteLocation); 639 } else { 640 log.debug("Correct Type 1 packet"); 641 currentByteLocation = processPacket(r, 0x01, packetTypeCmd, currentByteLocation); 642 } 643 if (log.isDebugEnabled()) { 644 StringBuilder buf = new StringBuilder(); 645 for (int i = oldstart; i < currentByteLocation; i++) { 646 buf.append(Integer.toHexString(r.getElement(i) & 0xff)).append(","); 647 } 648 log.debug("olstart - current hex {}", buf.toString()); 649 log.debug("--- finish packet {} ---", (currentByteLocation - 1)); 650 } 651 } 652 log.debug("==== Finish Processing Packet ===="); 653 } 654 655 public int processPacket(Dcc4PcReply r, int packetType, int packetTypeCmd, int currentByteLocation) { 656 // int packetType; 657 int dccpacketlength; 658 if (packetType == 0x02) { 659 dccpacketlength = (r.getElement(currentByteLocation) - 0x40) + 1; 660 } else { 661 dccpacketlength = (r.getElement(currentByteLocation)) + 1; 662 } 663 664 ++currentByteLocation; 665 666 int[] dcc_Data = new int[dccpacketlength]; 667 668 for (int i = 0; i < dccpacketlength; i++) { 669 dcc_Data[i] = r.getElement(currentByteLocation); 670 ++currentByteLocation; 671 } 672 try { 673 decodeDCCPacket(dcc_Data); 674 } catch (Exception ex) { 675 log.error("decodeDCCPacket Exception", ex); 676 } 677 678 if (packetType == 0x02) { 679 getInputState(Arrays.copyOfRange(r.getDataAsArray(), currentByteLocation, currentByteLocation + 4), address); 680 currentByteLocation = currentByteLocation + 4; 681 } 682 683 ArrayList<Dcc4PcReporter> railCommDataForSensor = new ArrayList<>(); 684 for (int i = 1; i < getNumEnabledSensors() + 1; i++) { 685 if (getReporterAtIndex(i).getRailComState() >= Dcc4PcSensor.ORIENTA) { 686 if (log.isDebugEnabled()) { 687 log.debug("Adding reporter for input {}", getReporterAtIndex(i).getSystemName()); 688 } 689 railCommDataForSensor.add(getReporterAtIndex(i)); 690 } 691 } 692 693 int railComDupPacket = (int) Math.ceil((railCommDataForSensor.size()) / 4.0f); 694 log.debug("We have {} Byte(s) to read on data", railComDupPacket); 695 696 log.debug("Now to handle the duplicate packet data"); 697 int j = 0; 698 699 for (int i = 0; i < railComDupPacket; i++) { 700 int inputNo = 0; 701 int dup = decodeDuplicatePacket(r.getElement(currentByteLocation), inputNo, railCommDataForSensor.get(j)); 702 if (log.isDebugEnabled()) { 703 log.debug("Input {} - {}", railCommDataForSensor.get(j).getDisplayName(), dup); 704 } 705 if (dup == 0) { 706 j++; 707 } else { 708 railCommDataForSensor.remove(j); 709 } 710 711 inputNo++; 712 if (j < railCommDataForSensor.size()) { 713 dup = decodeDuplicatePacket(r.getElement(currentByteLocation), inputNo, railCommDataForSensor.get(j)); 714 if (log.isDebugEnabled()) { 715 log.debug("Input {} - {}", railCommDataForSensor.get(j).getDisplayName(), dup); 716 } 717 if (dup == 0) { 718 j++; 719 } else { 720 railCommDataForSensor.remove(j); 721 } 722 inputNo++; 723 } 724 if (j < railCommDataForSensor.size()) { 725 dup = decodeDuplicatePacket(r.getElement(currentByteLocation), inputNo, railCommDataForSensor.get(j)); 726 if (log.isDebugEnabled()) { 727 log.debug("Input {} - {}", railCommDataForSensor.get(j).getDisplayName(), dup); 728 } 729 if (dup == 0) { 730 j++; 731 } else { 732 railCommDataForSensor.remove(j); 733 } 734 735 inputNo++; 736 } 737 if (j < railCommDataForSensor.size()) { 738 dup = decodeDuplicatePacket(r.getElement(currentByteLocation), inputNo, railCommDataForSensor.get(j)); 739 if (log.isDebugEnabled()) { 740 log.debug("Input {} - {}", railCommDataForSensor.get(j).getDisplayName(), dup); 741 } 742 if (dup == 0) { 743 j++; 744 } else { 745 railCommDataForSensor.remove(j); 746 } 747 } 748 currentByteLocation++; 749 } 750 751 if (log.isDebugEnabled()) { 752 for (Dcc4PcReporter dcc4PcReporter : railCommDataForSensor) { 753 log.debug("Data for sensor {}", dcc4PcReporter.getDisplayName()); 754 } 755 } 756 // re-use the variable to gather the size of each railcom input data 757 railComDupPacket = (int) Math.ceil((railCommDataForSensor.size()) / 2.0f); 758 log.debug("We have {} size byte(s) to read on data", railComDupPacket); 759 760 // This now becomes the length bytes for the rail comm information 761 j = 0; 762 for (int i = 0; i < railComDupPacket; i++) { 763 int tempValue = r.getElement(currentByteLocation) << (31 - 3); 764 tempValue = (tempValue >>> (31 - 3 + (0))); 765 railCommDataForSensor.get(j).setPacketLength(tempValue); 766 j++; 767 if (j < railCommDataForSensor.size()) { 768 tempValue = r.getElement(currentByteLocation) << (31 - 7); 769 tempValue = (tempValue >>> (31 - 7 + 4)); 770 railCommDataForSensor.get(j).setPacketLength(tempValue); 771 j++; 772 } 773 currentByteLocation++; 774 } 775 for (int i = 0; i < railCommDataForSensor.size(); i++) { 776 log.debug("railCommDataForSensor {} {}", railCommDataForSensor.get(i).getDisplayName(), railCommDataForSensor.get(i).getPacketLength()); 777 int[] arraytemp = new int[railCommDataForSensor.get(i).getPacketLength()]; 778 for (j = 0; j < railCommDataForSensor.get(i).getPacketLength(); j++) { 779 arraytemp[j] = 0xFF & r.getElement(currentByteLocation); 780 currentByteLocation++; 781 } 782 railCommDataForSensor.get(i).setPacket(arraytemp, dcc_addr_type, addr, cvNumber, speed, packetTypeCmd); 783 } 784 return currentByteLocation; 785 } 786 787 int addr = 0; 788 int dcc_addr_type = NO_ADDRESS; 789 int cvNumber = 0; 790 int speed = 0; 791 792 int[] lastDCCPacketSeen = new int[0]; 793 794 void decodeDCCPacket(int[] packet) { 795 if (Arrays.equals(packet, lastDCCPacketSeen)) { 796 return; 797 } 798 for (int idx = 0; idx < packet.length; ++idx) { 799 lastDCCPacketSeen = packet.clone(); 800 } 801 // lastDCCPacketSeen = packet; 802 speed = 0; 803 addr = 0; 804 dcc_addr_type = NO_ADDRESS; 805 if (log.isDebugEnabled()) { 806 807 StringBuilder buf = new StringBuilder(); 808 for (int i = 0; i < packet.length; ++i) { 809 buf.append(Integer.toHexString(packet[i])).append(","); 810 } 811 String s = buf.toString(); 812 log.debug("bytes to process {}", s); 813 } 814 // Basic Accessory Decoder packet 10aaaaaa 1aaacddd eeeeeeee 815 // Extended Accessory decoder packet starts 10aaaaaa 0aaa0aa1 000xxxxx eeeeeeee 816 int i = 0; 817 // Skip accessory decoder packets at this point 818 if ((packet[i] & 0xFF) == 0x00) { 819 return; 820 //Broadcast packet 821 } else if ((packet[i] & 0xff) == 0xff) { 822 // log.debug("Idle Packet"); 823 return; 824 } else if ((packet[i] & 0x80) == 0x80) { 825 // Accessory decoinfoder packet 826 if ((packet[i] & 0x80) == 0x80) { 827 // i++; don't increment as the for loop will do this 828 // basic Accessory Decoder packet one byte to follow 829 } else { 830 i++; 831 // TODO extended decoder packet two bytes 832 } 833 return; 834 } else { 835 int addr_1; 836 //The way that this determins of there is a two part extended address packet isn't great./ 837 if (packet.length > 0) { 838 addr_1 = packet[i] & 0xFF; 839 i++; 840 } else { 841 addr_1 = 0; 842 } 843 if (addr_1 == 0) { 844 dcc_addr_type = NO_ADDRESS; 845 } else if ((addr_1 >= 1) && (addr_1 <= 127)) { //Short address 846 dcc_addr_type = SHORT_ADDRESS; 847 addr = addr_1; 848 } else if ((addr_1 >= 128) && (addr_1 <= 191)) { 849 dcc_addr_type = NO_ADDRESS; //this is an accessory decoder address should have already of been filtered out 850 } else if ((addr_1 >= 192) && (addr_1 <= 231)) { //14 bit address 851 dcc_addr_type = LONG_ADDRESS; 852 addr = (((addr_1 & 0x3F) << 8) | (packet[i] & 0xFF)); 853 i++; 854 } else { 855 dcc_addr_type = NO_ADDRESS; 856 } 857 String addt; 858 switch (dcc_addr_type) { 859 case LONG_ADDRESS: 860 addt = "Long"; 861 break; 862 case SHORT_ADDRESS: 863 addt = "Short"; 864 break; 865 default: 866 addt = "No Address"; 867 break; 868 } 869 870 log.debug("DCC address type {} addr {}", addt, addr); 871 if ((dcc_addr_type != NO_ADDRESS)) { 872 log.debug("Current index {} value {}", i, packet[i] & 0xFF); 873 if ((packet[i] & 0xE0) == 0xE0) { 874 i++; 875 cvNumber = ((packet[i] & 0xff) + 1); 876 log.debug("CV Access cv:{}", cvNumber); 877 // two byte instruction when 1111 878 // three byte instuction when 1110 879 } else if ((packet[i] & 0xC0) == 0xC0) { 880 log.debug("Future"); 881 i++; 882 if ((packet[i] & 0x1F) == 0x1F) { 883 // F21-F28 Two byte instruction 884 i++; 885 } else if ((packet[i] & 0x1E) == 0x1E) { 886 // F13-F20 Two byte instruction 887 i++; 888 } else if ((packet[i] & 0x1D) == 0x1D) { 889 // Two byte instruction 890 i++; 891 892 } else if ((packet[i] >> 3) == 0x00) { 893 // This should fall through to 00000 three byte instruction 894 // i = i + 2; 895 896 } else { 897 // Remainder are reserved 898 } 899 // Two or three byte instruction 900 } else if ((packet[i] & 0xA0) == 0xa0) { 901 log.debug("For Function Group 2"); 902 if ((packet[i] & 0x10) == 0x10) { 903 log.debug("Functions 5 to 8"); 904 if ((packet[i] & 0x08) == 0x08) { 905 log.debug("Function 8 on"); 906 } else { 907 log.debug("Function 8 off"); 908 } 909 if ((packet[i] & 0x04) == 0x04) { 910 log.debug("Function 7 on"); 911 } else { 912 log.debug("Function 7 off"); 913 } 914 if ((packet[i] & 0x02) == 0x02) { 915 log.debug("Function 6 on"); 916 } else { 917 log.debug("Function 6 off"); 918 } 919 if ((packet[i] & 0x01) == 0x01) { 920 log.debug("Function 5 on"); 921 } else { 922 log.debug("Function 5 off"); 923 } 924 } else { 925 log.debug("Functions 9 to 12"); 926 if ((packet[i] & 0x08) == 0x08) { 927 log.debug("Function 12 on"); 928 } else { 929 log.debug("Function 12 off"); 930 } 931 if ((packet[i] & 0x04) == 0x04) { 932 log.debug("Function 11 on"); 933 } else { 934 log.debug("Function 11 off"); 935 } 936 if ((packet[i] & 0x02) == 0x02) { 937 log.debug("Function 10 on"); 938 } else { 939 log.debug("Function 10 off"); 940 } 941 if ((packet[i] & 0x01) == 0x01) { 942 log.debug("Function 9 on"); 943 } else { 944 log.debug("Function 9 off"); 945 } 946 } 947 // Single byte instruction 948 // i++; 949 } else if ((packet[i] & 0x80) == 0x80) { 950 log.debug("For Function Group 1"); 951 } else if ((packet[i] & 0x60) == 0x60) { 952 speed = (packet[i] & 0xff) - 0x60; 953 log.debug("Speed for forward 14 speed steps {}", speed); 954 //Only a single byte instruction 955 } else if ((packet[i] & 0x40) == 0x40) { 956 speed = ((packet[i] & 0xff) - 0x40); 957 log.debug("Speed for reverse 14 speed steps {}", speed); 958 //Only a single byte instruction 959 } else if ((packet[i] & 0x20) == 0x20) { 960 log.debug("Advanced Op"); 961 //Two byte instruction 962 if ((packet[i] & 0x1f) == 0x1f) { 963 i++; 964 log.debug("128 speed step control"); 965 if ((packet[i] & 0x80) == 0x80) { 966 speed = ((packet[i]) & 0xff) - 0x80; 967 log.debug("Forward {}", speed); 968 } else { 969 speed = ((packet[i]) & 0xff); 970 log.debug("Reverse {}", speed); 971 } 972 } 973 } else { 974 log.debug("Decoder and Consist Instruction"); 975 // decoder control is 0000 976 // consist control is 0001 977 } 978 } 979 980 } 981 log.debug("---End Decode DCC Packet---"); 982 } 983 984 int getInputs() { 985 return inputs; 986 } 987 988 String getDescription() { 989 return description; 990 } 991 992 String getVersion() { 993 return version; 994 } 995 } 996 997 public List<Integer> getBoards() { 998 ArrayList<Integer> list = new ArrayList<>(); 999 Enumeration<Integer> keys = activeBoards.keys(); 1000 while (keys.hasMoreElements()) { 1001 Integer key = keys.nextElement(); 1002 list.add(key); 1003 } // end while 1004 return list; 1005 } 1006 1007 public int getBoardInputs(int board) { 1008 if (!activeBoards.containsKey(board)) { 1009 return -1; 1010 } 1011 return activeBoards.get(board).getInputs(); 1012 } 1013 1014 public String getBoardEncodingAsString(int board) { 1015 if (!activeBoards.containsKey(board)) { 1016 return "unknown"; 1017 } 1018 return activeBoards.get(board).getEncodingAsString(); 1019 } 1020 1021 public String getBoardVersion(int board) { 1022 if (!activeBoards.containsKey(board)) { 1023 return "unknown"; 1024 } 1025 return activeBoards.get(board).getVersion(); 1026 } 1027 1028 public String getBoardDescription(int board) { 1029 if (!activeBoards.containsKey(board)) { 1030 return "unknown"; 1031 } 1032 return activeBoards.get(board).getDescription(); 1033 } 1034 1035 protected boolean isBoardCreated(int address) { 1036 return activeBoards.containsKey(address); 1037 } 1038 1039 protected void addActiveBoard(int address, String version, int inputs, int encoding) { 1040 activeBoards.put(address, new ActiveBoard(address, version, inputs, encoding)); 1041 } 1042 1043 protected void setBoardDescription(int address, String description) { 1044 ActiveBoard board = activeBoards.get(address); 1045 board.setDescription(description); 1046 } 1047 1048 protected void createSensorsForBoard(Dcc4PcReply r) { 1049 if (r.getBoard() == -1) { 1050 log.debug("Reply has no board associated with it"); 1051 return; 1052 } 1053 class SensorMaker implements Runnable { 1054 1055 Dcc4PcReply reply; 1056 1057 SensorMaker(Dcc4PcReply r) { 1058 reply = r; 1059 } 1060 1061 @Override 1062 public void run() { 1063 createSensorsFromReply(reply); 1064 } 1065 } 1066 1067 Thread thr = new Thread(new SensorMaker(r), "Dcc4PCSensor Maker board " + r.getBoard()); 1068 try { 1069 thr.start(); 1070 } catch (java.lang.IllegalThreadStateException ex) { 1071 log.error("Exception {} {}", thr.getName(), ex.getMessage()); 1072 } 1073 } 1074 1075 /** 1076 * Validates to contain at least 1 number . . . 1077 * <p> 1078 * TODO: Custom validation for Dcc4PcSensorManager could be improved. 1079 * {@inheritDoc} 1080 */ 1081 @Override 1082 @Nonnull 1083 public String validateSystemNameFormat(@Nonnull String name, @Nonnull java.util.Locale locale) throws jmri.NamedBean.BadSystemNameException { 1084 return validateTrimmedMin1NumberSystemNameFormat(name,locale); 1085 } 1086 1087 @Override 1088 public void dispose() { 1089 stopPolling(); 1090 super.dispose(); 1091 } 1092 1093 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Dcc4PcSensorManager.class); 1094 1095}