001package jmri.jmrix.dcc4pc; 002 003import java.util.Collection; 004import java.util.Collections; 005import java.util.Hashtable; 006import java.util.List; 007import java.util.Map; 008import java.util.concurrent.CopyOnWriteArrayList; 009import jmri.CollectingReporter; 010import jmri.DccLocoAddress; 011import jmri.IdTag; 012import jmri.RailCom; 013import jmri.Reporter; 014import jmri.Sensor; 015import jmri.implementation.AbstractRailComReporter; 016 017/** 018 * Extend jmri.implementation.AbstractRailComReporter for Dcc4Pc Reporters. 019 * Implementation for providing status of rail com decoders at this 020 * reporter location. 021 * <p> 022 * The reporter will decode the rail com packets and add the information to the 023 * rail com tag. 024 * 025 * @author Kevin Dickerson Copyright (C) 2012 026 */ 027public class Dcc4PcReporter extends AbstractRailComReporter implements CollectingReporter { 028 029 public Dcc4PcReporter(String systemName, String userName) { // a human-readable Reporter number must be specified! 030 super(systemName, userName); // can't use prefix here, as still in construction 031 } 032 033 // The locos in this block, oldest first. Dcc4PcBlockLocos decides who is 034 // present, from channel 2 replies and clean channel 1 addresses, so that 035 // several locos in one block can be told apart. 036 // It's changed on the packet decode thread and read by the GUI (the Reporter 037 // table), so it's a copy-on-write list and readers always see a consistent 038 // snapshot. 039 private final List<IdTag> entryOrder = new CopyOnWriteArrayList<>(); 040 041 /** @return the locos in this block, oldest first, as one consistent snapshot */ 042 private IdTag[] entrySnapshot() { 043 return entryOrder.toArray(new IdTag[0]); 044 } 045 046 /** 047 * {@inheritDoc} 048 */ 049 @Override 050 @SuppressWarnings("unchecked") 051 public Collection<Object> getCollection() { 052 return (Collection<Object>) (Collection<?>) Collections.unmodifiableCollection(entryOrder); 053 } 054 055 private static RailCom tagFor(int address) { 056 return (RailCom) jmri.InstanceManager.getDefault(jmri.RailComManager.class).provideIdTag("" + address); 057 } 058 059 /** 060 * A loco has been found in this block. 061 * 062 * @param address loco address 063 * @param isLong true for a long (14-bit) address 064 */ 065 void locoEntered(int address, boolean isLong) { 066 RailCom tag = tagFor(address); 067 tag.setDccAddress(new DccLocoAddress(address, isLong)); 068 RailCom.Orientation o = orientation(); 069 if (o != null) { 070 tag.setOrientation(o); 071 } 072 entryOrder.remove(tag); 073 entryOrder.add(tag); 074 notify(tag); 075 } 076 077 /** 078 * A loco already in this block has been heard again: update its "last 079 * seen" time. This doesn't make it the current report, so with several 080 * locos here the report doesn't flip between them. 081 * 082 * @param address loco address 083 */ 084 void locoHeard(int address) { 085 RailCom tag = tagFor(address); 086 if (entryOrder.contains(tag)) { 087 tag.setWhereLastSeen(this); 088 } 089 } 090 091 /** 092 * A loco has left this block. 093 * 094 * @param address loco address 095 */ 096 void locoExited(int address) { 097 RailCom tag = tagFor(address); 098 if (!entryOrder.remove(tag)) { 099 return; 100 } 101 if (getCurrentReport() == tag) { 102 updateCurrentReportAfterExit(); 103 } else { 104 notifyCollectionUpdated(); 105 } 106 } 107 108 /** 109 * Report the most recently entered loco still in this block, as 110 * {@link jmri.jmrix.openlcb.OlcbReporter} does. 111 */ 112 private void updateCurrentReportAfterExit() { 113 IdTag[] tags = entrySnapshot(); 114 if (tags.length == 0) { 115 notify(null); 116 return; 117 } 118 for (int i = tags.length - 1; i >= 0; i--) { 119 IdTag candidate = tags[i]; 120 if (candidate.getWhereLastSeen() == this) { 121 notify(candidate); 122 return; 123 } 124 } 125 IdTag fallback = tags[tags.length - 1]; 126 setReport(fallback); 127 setState(IdTag.SEEN); 128 } 129 130 /** 131 * {@inheritDoc} 132 * <p> 133 * Exits are reported by {@link #locoExited(int)}, so a loco seen elsewhere 134 * stays in the collection until then; only the current report moves on. 135 */ 136 @Override 137 public void notifySeenElsewhere(IdTag tag, Reporter newReporter) { 138 if (!this.equals(newReporter) && getCurrentReport() == tag) { 139 IdTag[] tags = entrySnapshot(); 140 for (int i = tags.length - 1; i >= 0; i--) { 141 IdTag candidate = tags[i]; 142 if (candidate != tag && candidate.getWhereLastSeen() == this) { 143 notify(candidate); 144 return; 145 } 146 } 147 } 148 } 149 150 /** 151 * The report text is worked out when a loco is reported; if that loco is 152 * the current report and its data (such as speed) changes later, work it 153 * out again so the Reporter table shows the new value. 154 */ 155 private void refreshReport(RailCom tag) { 156 if (getCurrentReport() == tag) { 157 setReport(tag); 158 firePropertyChange("currentReport", null, tag); 159 } 160 } 161 162 /** @return the orientation the input reports, or null if it reports none */ 163 private RailCom.Orientation orientation() { 164 if (state == REPRESENTS_RAILCOM_EAST) { 165 return RailCom.Orientation.EAST; 166 } else if (state == REPRESENTS_RAILCOM_WEST) { 167 return RailCom.Orientation.WEST; 168 } 169 return null; 170 } 171 172 /** 173 * {@inheritDoc} 174 */ 175 @Override 176 public boolean hasExitReports() { 177 return true; 178 } 179 180 // data members 181 transient RailComPacket[] rcPacket = new RailComPacket[3]; 182 183 void setPacket(int[] arraytemp, int dcc_addr_type, int dcc_addr, int cvNumber, int speed, int packetTypeCmd) { 184 log.debug("{} dcc_addr {} {} {}", getDisplayName(), dcc_addr, cvNumber, speed); 185 RailComPacket rc = new RailComPacket(arraytemp, dcc_addr_type, dcc_addr, cvNumber, speed); 186 decodeRailComInfo(rc, packetTypeCmd); 187 rcPacket[2] = rcPacket[1]; 188 rcPacket[1] = rcPacket[0]; 189 rcPacket[0] = rc; 190 synchronized(lock) { 191 log.debug("Packets Seen {} in error {}", packetseen, packetsinerror); 192 } 193 } 194 195 static class RailComPacket { 196 197 transient final int[] rcPacket; 198 int dcc_addr_type; 199 int dccAddress; 200 int cvNumber; 201 int speed; 202 203 RailComPacket(int[] array, int dcc_addr_type, int dcc_addr, int cvNumber, int speed) { 204 rcPacket = array; 205 this.dcc_addr_type = dcc_addr_type; 206 this.dccAddress = dcc_addr; 207 this.cvNumber = cvNumber; 208 this.speed = speed; 209 } 210 211 int[] getPacket() { 212 return rcPacket; 213 } 214 215 int getAddressType() { 216 return dcc_addr_type; 217 } 218 219 int getDccAddress() { 220 return dccAddress; 221 } 222 223 int getCvNumber() { 224 return cvNumber; 225 } 226 227 int getSpeed() { 228 return speed; 229 } 230 231 String toHexString() { 232 StringBuilder buf = new StringBuilder(); 233 buf.append("0x").append(Integer.toHexString(0xFF & rcPacket[0])); 234 for (int i = 1; i < rcPacket.length; i++) { 235 buf.append(", 0x").append(Integer.toHexString(0xFF & rcPacket[i])); 236 } 237 return buf.toString(); 238 } 239 } 240 241 void duplicatePacket(int dup) { 242 RailComPacket temp; 243 switch (dup) { 244 case 0x00: 245 break; //re-use the rcPacket at the head. 246 case 0x02: 247 temp = rcPacket[1]; //move rcPacket one to the head 248 rcPacket[1] = rcPacket[0]; 249 rcPacket[0] = temp; 250 break; 251 case 0x03: 252 temp = rcPacket[2]; //move rcPacket two to the head 253 rcPacket[2] = rcPacket[1]; 254 rcPacket[1] = rcPacket[0]; 255 rcPacket[0] = temp; 256 break; 257 default: 258 break; 259 } 260 } 261 262 int state = Sensor.UNKNOWN; 263 264 static final int REPRESENTS_RAILCOM_EAST = 0x10; 265 static final int REPRESENTS_RAILCOM_WEST = 0x20; 266 267 public void setRailComState(int ori) { 268 if (state == ori) { 269 return; 270 } 271 state = ori; 272 if (ori == Sensor.INACTIVE || ori == Sensor.UNKNOWN) { 273 //We reset everything as the associated sensor has gone inactive 274 synchronized (this) { 275 addr = 0; 276 address_part_1 = 0; // no ADR_HIGH yet; 0x100 would pass for short-address high byte 0 277 address_part_2 = -1; 278 addr_type = -1; 279 actual_speed = -1; 280 actual_load = -1; 281 actual_temperature = -1; 282 fuelLevel = -1; 283 waterLevel = -1; 284 location = -1; 285 routing_no = -1; 286 } 287 cvNumber = -1; 288 cvValues = new Hashtable<>(); 289 entryOrder.clear(); 290 setReport(null); 291 } else if (ori == REPRESENTS_RAILCOM_EAST || ori == REPRESENTS_RAILCOM_WEST) { 292 // the reader gives one orientation per input, so it applies to every loco here 293 RailCom.Orientation orientation = orientation(); 294 for (IdTag t : entryOrder) { 295 ((RailCom) t).setOrientation(orientation); 296 } 297 if (super.getCurrentReport() instanceof RailCom) { 298 ((RailCom) super.getCurrentReport()).setOrientation(orientation); 299 } 300 firePropertyChange("currentReport", null, null); 301 } 302 } 303 304 public int getRailComState() { 305 return state; 306 } 307 308 public String getReport() { 309 if (super.getCurrentReport() != null && super.getCurrentReport() instanceof RailCom) { 310 return ((RailCom) super.getCurrentReport()).getTagID(); 311 } 312 if ((getRailComState() < REPRESENTS_RAILCOM_EAST) || (rcPacket[0] == null) || rcPacket[0].getPacket() == null) { 313 return ""; 314 } 315 return ""; 316 } 317 318 //packet Length is a temp store used for decoding the railcom packet 319 int packetLength = 0; 320 321 void setPacketLength(int i) { 322 packetLength = i; 323 } 324 325 int getPacketLength() { 326 return packetLength; 327 } 328 329 int addr = 0; 330 int address_part_1 = 0; // 0x100 bit set once an ADR_HIGH has arrived 331 int address_part_2 = -1; 332 int addr_type = -1; 333 int actual_speed = -1; 334 int actual_load = -1; 335 int actual_temperature = -1; 336 int fuelLevel = -1; 337 int waterLevel = -1; 338 int location = -1; 339 int routing_no = -1; 340 int cvNumber = -1; 341 int cvvalue = -1; 342 343 int addressp1found = 0; 344 345 static int packetseen = 0; 346 static int packetsinerror = 0; 347 private static final Object lock = new Object(); 348 349 Hashtable<Integer, Integer> cvValues = new Hashtable<>(); 350 351 /** 352 * The first two bytes of a cutout's data are channel 1. Channel 2 is a 353 * separate time slot, so a bad byte in channel 1 (often two decoders' 354 * address broadcasts colliding) mustn't throw away channel 2: on 355 * 2026-10-13 that discarded POM answers that came with channel 1 356 * {@code 95 04}. 357 */ 358 static final int CHANNEL_1_LENGTH = 2; 359 360 void decodeRailComInfo(RailComPacket rc, int packetTypeCmd) { 361 362 synchronized(lock) { 363 addressp1found++; 364 RailCom rcTag = null; 365 366 if (super.getCurrentReport() instanceof RailCom) { 367 rcTag = (RailCom) super.getCurrentReport(); 368 } 369 // Channel 2 data (POM values, DYN) comes from the loco the DCC packet was sent 370 // to, which with several locos here needn't be the current report. 371 // Only that decoder replies on channel 2, so if it isn't one of the locos here, 372 // the data belongs to nobody here; never give it to whichever loco is current. 373 RailCom ch2Tag = null; 374 if (rc.getDccAddress() > 0) { 375 RailCom dest = tagFor(rc.getDccAddress()); 376 if (entryOrder.contains(dest)) { 377 ch2Tag = dest; 378 } 379 } 380 int[] packet = rc.getPacket(); 381 char chbyte; 382 char type; 383 384 if (log.isDebugEnabled()) { 385 log.debug("{} packet type {}", getDisplayName(), packetTypeCmd); 386 log.debug("decodeRailComInfo {} {}", this.getDisplayName(), super.getCurrentReport()); 387 StringBuilder buf = new StringBuilder(); 388 for (int i = 0; i < packet.length; ++i) { 389 buf.append(packet[i]); 390 } 391 log.debug("Rail Comm Packets {}", buf); 392 393 } 394 int i = 0; 395 while (i < packet.length) { 396 packetseen++; 397 chbyte = (char) packet[i]; 398 chbyte = decode[chbyte]; 399 if (chbyte == ERROR) { 400 if (log.isDebugEnabled()) { 401 log.debug("{} Error packet stage 1: {}", this.getDisplayName(), Integer.toHexString(packet[i])); 402 } 403 packetsinerror++; 404 if (i < CHANNEL_1_LENGTH) { 405 i = CHANNEL_1_LENGTH; // channel 2 is a separate slot: still decode it 406 continue; 407 } 408 return; 409 } 410 i++; 411 if ((chbyte & ACK) == ACK) { 412 // ACK, NACK or BUSY: a single symbol, and channel 2 may hold several 413 continue; 414 } 415 if (packet.length <= i) { 416 break; 417 } 418 type = chbyte; 419 chbyte = (char) packet[i]; 420 chbyte = decode[chbyte]; 421 if ((chbyte == ERROR) || ((chbyte & ACK) == ACK)) { 422 if (log.isDebugEnabled()) { 423 log.debug("{} Error packet stage 3 {}\n{}", this.getDisplayName(), Integer.toHexString(packet[i]), rc.toHexString()); 424 } 425 i++; 426 packetsinerror++; 427 if (i <= CHANNEL_1_LENGTH) { 428 i = CHANNEL_1_LENGTH; // channel 2 is a separate slot: still decode it 429 continue; 430 } 431 return; 432 } 433 434 chbyte = (char) (((type & 0x03) << 6) | (chbyte & 0x3f)); 435 type = (char) ((type >> 2) & 0x0F); 436 // RCN-217: EXT (3) and DYN (7) datagrams are three symbols long and 437 // IDs 8-11 six; the rest are two. Skipping the extra symbols stops 438 // them being read as the start of another datagram, such as a 439 // bogus ADR_HIGH from a DYN subindex. 440 int extraSymbols = 0; 441 if (type == 3 || type == 7) { 442 extraSymbols = 1; 443 } else if (type >= 8 && type <= 11) { 444 extraSymbols = 4; 445 } 446 447 switch (type) { 448 case 0: 449 log.debug("{} CV Value {}{}", this.getDisplayName(), (int) chbyte, ch2Tag); 450 cvvalue = chbyte; 451 if (ch2Tag != null && rc.getCvNumber() > 0) { 452 // Only the reply to a POM packet for the CV being read or written 453 // answers it: replies to earlier POM packets, still being repeated, 454 // arrive too, and were taken as the answer (2026-10-13). 455 if (ch2Tag.getExpectedCv() == rc.getCvNumber()) { 456 ch2Tag.setCvValue(chbyte); 457 } else { 458 ch2Tag.setCV(rc.getCvNumber(), chbyte); 459 } 460 } 461 break; 462 case 4: 463 if (log.isDebugEnabled()) { 464 log.debug("{} Create/Get id tag for {}", this.getDisplayName(), rc.getDccAddress()); 465 } 466 addr = rc.getDccAddress(); 467 addr_type = rc.getAddressType(); 468 break; 469 case 1: // Address byte 1 470 log.debug("Address Byte 1"); 471 // only 0 (short), 10xxxxxx (long) or 0110xxxx (consist) are addresses 472 if (chbyte != 0 && (chbyte & 0xC0) != 0x80 && (chbyte & 0xF0) != 0x60) { 473 log.debug("{} Ignoring invalid address byte 1 {}", this.getDisplayName(), Integer.toHexString(chbyte)); 474 break; 475 } 476 address_part_1 = (0x100 | chbyte); 477 addressp1found = 0; 478 break; 479 case 2: //Address byte 2 480 log.debug("{} Address part 2:", this.getDisplayName()); 481 address_part_2 = chbyte; 482 if (packetTypeCmd == 0x03) { 483 log.debug("Type three packet so shouldn't not pair part one with part two if it came from the previous packet"); 484 //As the last command was a type 3, an address part one packet can not be paired with this address part two packet. Therefore will set it back to default 485 //address_part_1 = 0x100; 486 // break; 487 } 488 if (!((address_part_1 & 0x100) == 0x100)) { 489 log.debug("{} Break at Address part 1, part one not complete", this.getDisplayName()); 490 break; 491 } 492 addr = combineAddress(address_part_1 & 0xFF, address_part_2); 493 rcTag = decodeAddress(); 494 break; 495 case 3: // EXT (RCN-217), location information; not decoded yet 496 log.debug("{} EXT datagram", this.getDisplayName()); 497 break; 498 case 5: //Routing number 499 routing_no = chbyte; 500 if (rcTag != null) { 501 rcTag.setRoutingNo(routing_no); 502 rcTag.setWhereLastSeen(this); 503 } 504 break; 505 case 6: //Location 506 location = chbyte; 507 if (rcTag != null) { 508 rcTag.setLocation(location); 509 rcTag.setWhereLastSeen(this); 510 } 511 break; 512 case 7: // DYN (RCN-217): 8-bit value, then a 6-bit subindex in the third symbol 513 if (i + 1 >= packet.length || decode[packet[i + 1]] == ERROR 514 || (decode[packet[i + 1]] & ACK) == ACK) { 515 log.debug("{} DYN datagram without a valid subindex", this.getDisplayName()); 516 break; 517 } 518 int subindex = decode[packet[i + 1]] & 0x3F; 519 log.debug("{} DYN subindex {} value {}", this.getDisplayName(), subindex, (int) chbyte); 520 if (subindex == 0) { // actual speed 0-255 521 actual_speed = chbyte; 522 } else if (subindex == 1) { // actual speed 256-511 523 actual_speed = chbyte + 256; 524 } else { 525 break; // other DYN values are not decoded yet 526 } 527 if (ch2Tag != null && ch2Tag.getActualSpeed() != actual_speed) { 528 ch2Tag.setActualSpeed(actual_speed); 529 refreshReport(ch2Tag); 530 } 531 break; 532 case 8: // XPOM (RCN-217, IDs 8-11); not decoded yet 533 case 9: 534 case 10: 535 case 11: 536 log.debug("{} XPOM datagram", this.getDisplayName()); 537 break; 538 case 15: //CV Address Value 539 log.debug("{} CV Address and value:", this.getDisplayName()); 540 i = i + 2; 541 //len = 4; 542 break; 543 default: 544 log.debug("unknown railcom type packet {}", type); 545 break; 546 } 547 i += 1 + extraSymbols; 548 549 } 550 } 551 } 552 553 RailCom decodeAddress() { 554 RailCom rcTag; 555 log.debug("{} Create/Get id tag for {}", this.getDisplayName(), addr); 556 rcTag = (RailCom)jmri.InstanceManager.getDefault(jmri.RailComManager.class).provideIdTag("" + addr); 557 558 if ((fuelLevel != -1)) { 559 rcTag.setFuelLevel(fuelLevel); 560 } 561 if ((waterLevel != -1)) { 562 rcTag.setWaterLevel(waterLevel); 563 } 564 if ((routing_no != -1)) { 565 rcTag.setRoutingNo(routing_no); 566 } 567 if ((location != -1)) { 568 rcTag.setLocation(location); 569 } 570 if ((actual_temperature != -1)) { 571 rcTag.setActualTemperature(actual_temperature); 572 } 573 if ((actual_load != -1)) { 574 rcTag.setActualLoad(actual_load); 575 } 576 if ((actual_speed != -1)) { 577 rcTag.setActualSpeed(actual_speed); 578 } 579 for (Map.Entry<Integer, Integer> entry : cvValues.entrySet()) { 580 rcTag.setCV(entry.getKey(), entry.getValue()); 581 if (cvvalue != -1) { 582 rcTag.setCvValue(cvvalue); 583 } 584 } 585 586 address_part_1 = 0; 587 address_part_2 = -1; 588 // No notify(rcTag) here: Dcc4PcBlockLocos decides which locos are present 589 // and calls locoEntered(), which also sets the orientation. 590 return rcTag; 591 } 592 593 /** 594 * Combine the ADR_HIGH and ADR_LOW payloads of a RailCom channel 1 595 * broadcast into a locomotive address (RCN-217 5.2.1). 596 * 597 * @param high ADR_HIGH payload: 0 for a short address, 10xxxxxx for the 598 * top six bits of a long address, 0110xxxx for a consist 599 * @param low ADR_LOW payload 600 * @return the address 601 */ 602 static int combineAddress(int high, int low) { 603 if ((high & 0xC0) == 0x80) { 604 return ((high & 0x3F) << 8) | (low & 0xFF); 605 } 606 return low & 0x7F; 607 } 608 609 RailCom provideTag(int address, int addr_type) { 610 log.debug("provide Tag"); 611 RailCom rcTag = (RailCom) jmri.InstanceManager.getDefault(jmri.RailComManager.class).provideIdTag("" + address); 612 notify(rcTag); 613 return rcTag; 614 } 615 616 public static final char ACK = 0x80; 617 public static final char ACK_1 = 0x81; 618 public static final char ACK_2 = 0x82; 619 public static final char ACK_3 = 0x83; 620 public static final char ACK_4 = 0x84; 621 public static final char ACK_5 = 0x85; 622 public static final char ACK_6 = 0x86; 623 public static final char ERROR = 0xFF; 624 625 private static final char[] decode = new char[]{ 626 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 627 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ACK_1, 628 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 0x33, 629 ERROR, ERROR, ERROR, 0x34, ERROR, 0x35, 0x36, ERROR, 630 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 0x3A, 631 ERROR, ERROR, ERROR, 0x3B, ERROR, 0x3C, 0x37, ERROR, 632 ERROR, ERROR, ERROR, 0x3F, ERROR, 0x3D, 0x38, ERROR, 633 ERROR, 0x3E, 0x39, ERROR, ACK_5, ERROR, ERROR, ERROR, 634 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 0x24, 635 ERROR, ERROR, ERROR, 0x23, ERROR, 0x22, 0x21, ERROR, 636 ERROR, ERROR, ERROR, 0x1F, ERROR, 0x1E, 0x20, ERROR, 637 ERROR, 0x1D, 0x1C, ERROR, 0x1B, ERROR, ERROR, ERROR, 638 ERROR, ERROR, ERROR, 0x19, ERROR, 0x18, 0x1A, ERROR, 639 ERROR, 0x17, 0x16, ERROR, 0x15, ERROR, ERROR, ERROR, 640 ERROR, 0x25, 0x14, ERROR, 0x13, ERROR, ERROR, ERROR, 641 0x32, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 642 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ACK_2, 643 ERROR, ERROR, ERROR, 0x0E, ERROR, 0x0D, 0x0C, ERROR, 644 ERROR, ERROR, ERROR, 0x0A, ERROR, 0x09, 0x0B, ERROR, 645 ERROR, 0x08, 0x07, ERROR, 0x06, ERROR, ERROR, ERROR, 646 ERROR, ERROR, ERROR, 0x04, ERROR, 0x03, 0x05, ERROR, 647 ERROR, 0x02, 0x01, ERROR, 0x00, ERROR, ERROR, ERROR, 648 ERROR, 0x0F, 0x10, ERROR, 0x11, ERROR, ERROR, ERROR, 649 0x12, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 650 ERROR, ERROR, ERROR, ACK_3, ERROR, 0x2B, 0x30, ERROR, 651 ERROR, 0x2A, 0x2F, ERROR, 0x31, ERROR, ERROR, ERROR, 652 ERROR, 0x29, 0x2E, ERROR, 0x2D, ERROR, ERROR, ERROR, 653 0x2C, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 654 ERROR, ACK_6, 0x28, ERROR, 0x27, ERROR, ERROR, ERROR, 655 0x26, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 656 ACK_4, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 657 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR}; 658 659 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Dcc4PcReporter.class); 660 661}