001package jmri.jmrix.dcc4pc; 002 003import java.util.Hashtable; 004import java.util.Map; 005import jmri.RailCom; 006import jmri.Sensor; 007import jmri.implementation.AbstractRailComReporter; 008 009/** 010 * Extend jmri.implementation.AbstractRailComReporter for Dcc4Pc Reporters. 011 * Implementation for providing status of rail com decoders at this 012 * reporter location. 013 * <p> 014 * The reporter will decode the rail com packets and add the information to the 015 * rail com tag. 016 * 017 * @author Kevin Dickerson Copyright (C) 2012 018 */ 019public class Dcc4PcReporter extends AbstractRailComReporter { 020 021 public Dcc4PcReporter(String systemName, String userName) { // a human-readable Reporter number must be specified! 022 super(systemName, userName); // can't use prefix here, as still in construction 023 } 024 025 // data members 026 transient RailComPacket[] rcPacket = new RailComPacket[3]; 027 028 void setPacket(int[] arraytemp, int dcc_addr_type, int dcc_addr, int cvNumber, int speed, int packetTypeCmd) { 029 log.debug("{} dcc_addr {} {} {}", getDisplayName(), dcc_addr, cvNumber, speed); 030 RailComPacket rc = new RailComPacket(arraytemp, dcc_addr_type, dcc_addr, cvNumber, speed); 031 decodeRailComInfo(rc, packetTypeCmd); 032 rcPacket[2] = rcPacket[1]; 033 rcPacket[1] = rcPacket[0]; 034 rcPacket[0] = rc; 035 synchronized(lock) { 036 log.debug("Packets Seen {} in error {}", packetseen, packetsinerror); 037 } 038 } 039 040 static class RailComPacket { 041 042 transient final int[] rcPacket; 043 int dcc_addr_type; 044 int dccAddress; 045 int cvNumber; 046 int speed; 047 048 RailComPacket(int[] array, int dcc_addr_type, int dcc_addr, int cvNumber, int speed) { 049 rcPacket = array; 050 this.dcc_addr_type = dcc_addr_type; 051 this.dccAddress = dcc_addr; 052 this.cvNumber = cvNumber; 053 this.speed = speed; 054 } 055 056 int[] getPacket() { 057 return rcPacket; 058 } 059 060 int getAddressType() { 061 return dcc_addr_type; 062 } 063 064 int getDccAddress() { 065 return dccAddress; 066 } 067 068 int getCvNumber() { 069 return cvNumber; 070 } 071 072 int getSpeed() { 073 return speed; 074 } 075 076 String toHexString() { 077 StringBuilder buf = new StringBuilder(); 078 buf.append("0x").append(Integer.toHexString(0xFF & rcPacket[0])); 079 for (int i = 1; i < rcPacket.length; i++) { 080 buf.append(", 0x").append(Integer.toHexString(0xFF & rcPacket[i])); 081 } 082 return buf.toString(); 083 } 084 } 085 086 void duplicatePacket(int dup) { 087 RailComPacket temp; 088 switch (dup) { 089 case 0x00: 090 break; //re-use the rcPacket at the head. 091 case 0x02: 092 temp = rcPacket[1]; //move rcPacket one to the head 093 rcPacket[1] = rcPacket[0]; 094 rcPacket[0] = temp; 095 break; 096 case 0x03: 097 temp = rcPacket[2]; //move rcPacket two to the head 098 rcPacket[2] = rcPacket[1]; 099 rcPacket[1] = rcPacket[0]; 100 rcPacket[0] = temp; 101 break; 102 default: 103 break; 104 } 105 } 106 107 int state = Sensor.UNKNOWN; 108 109 static final int REPRESENTS_RAILCOM_ORIENTA = 0x10; 110 static final int REPRESENTS_RAILCOM_ORIENTB = 0x20; 111 112 public void setRailComState(int ori) { 113 if (state == ori) { 114 return; 115 } 116 state = ori; 117 if (ori == Sensor.INACTIVE || ori == Sensor.UNKNOWN) { 118 //We reset everything as the associated sensor has gone inactive 119 synchronized (this) { 120 addr = 0; 121 address_part_1 = 0x100; 122 address_part_2 = -1; 123 addr_type = -1; 124 actual_speed = -1; 125 actual_load = -1; 126 actual_temperature = -1; 127 fuelLevel = -1; 128 waterLevel = -1; 129 location = -1; 130 routing_no = -1; 131 } 132 cvNumber = -1; 133 cvValues = new Hashtable<>(); 134 setReport(null); 135 } else if (ori == REPRESENTS_RAILCOM_ORIENTA || ori == REPRESENTS_RAILCOM_ORIENTB) { 136 if (super.getCurrentReport() != null && super.getCurrentReport() instanceof RailCom) { 137 var orientation = RailCom.Orientation.ORIENTA; 138 if (ori == REPRESENTS_RAILCOM_ORIENTB) orientation = RailCom.Orientation.ORIENTB; 139 140 ((RailCom) super.getCurrentReport()).setOrientation(orientation); 141 } 142 firePropertyChange("currentReport", null, null); 143 } 144 } 145 146 public int getRailComState() { 147 return state; 148 } 149 150 public String getReport() { 151 if (super.getCurrentReport() != null && super.getCurrentReport() instanceof RailCom) { 152 return ((RailCom) super.getCurrentReport()).getTagID(); 153 } 154 if ((getRailComState() < REPRESENTS_RAILCOM_ORIENTA) || (rcPacket[0] == null) || rcPacket[0].getPacket() == null) { 155 return ""; 156 } 157 return ""; 158 } 159 160 //packet Length is a temp store used for decoding the railcom packet 161 int packetLength = 0; 162 163 void setPacketLength(int i) { 164 packetLength = i; 165 } 166 167 int getPacketLength() { 168 return packetLength; 169 } 170 171 int addr = 0; 172 int address_part_1 = 0x100; 173 int address_part_2 = -1; 174 int addr_type = -1; 175 int actual_speed = -1; 176 int actual_load = -1; 177 int actual_temperature = -1; 178 int fuelLevel = -1; 179 int waterLevel = -1; 180 int location = -1; 181 int routing_no = -1; 182 int cvNumber = -1; 183 int cvvalue = -1; 184 185 int addressp1found = 0; 186 187 static int packetseen = 0; 188 static int packetsinerror = 0; 189 private static final Object lock = new Object(); 190 191 Hashtable<Integer, Integer> cvValues = new Hashtable<>(); 192 193 void decodeRailComInfo(RailComPacket rc, int packetTypeCmd) { 194 195 synchronized(lock) { 196 addressp1found++; 197 RailCom rcTag = null; 198 199 if (super.getCurrentReport() instanceof RailCom) { 200 rcTag = (RailCom) super.getCurrentReport(); 201 } 202 int[] packet = rc.getPacket(); 203 char chbyte; 204 char type; 205 206 if (log.isDebugEnabled()) { 207 log.debug("{} packet type {}", getDisplayName(), packetTypeCmd); 208 log.debug("decodeRailComInfo {} {}", this.getDisplayName(), super.getCurrentReport()); 209 StringBuilder buf = new StringBuilder(); 210 for (int i = 0; i < packet.length; ++i) { 211 buf.append(packet[i]); 212 } 213 log.debug("Rail Comm Packets {}", buf); 214 215 } 216 int i = 0; 217 while (i < packet.length) { 218 packetseen++; 219 chbyte = (char) packet[i]; 220 chbyte = decode[chbyte]; 221 if (chbyte == ERROR) { 222 if (log.isDebugEnabled()) { 223 log.debug("{} Error packet stage 1: {}", this.getDisplayName(), Integer.toHexString(packet[i])); 224 } 225 packetsinerror++; 226 return; 227 } 228 i++; 229 if ((chbyte & ACK) == ACK) { 230 chbyte = (char) packet[i]; 231 i++; 232 chbyte = decode[chbyte]; 233 if (chbyte == ERROR) { 234 log.debug("{} Error packet stage 2", this.getDisplayName()); 235 packetsinerror++; 236 return; 237 } 238 if ((chbyte & ACK) == ACK) { 239 if (packet.length <= (i + 1)) { 240 log.debug("No further data to process Only had the ack 1"); 241 break; 242 } 243 chbyte = (char) packet[i]; 244 i++; 245 chbyte = decode[chbyte]; 246 } 247 } 248 if (packet.length <= i) { 249 break; 250 } 251 type = chbyte; 252 chbyte = (char) packet[i]; 253 chbyte = decode[chbyte]; 254 if ((chbyte == ERROR) || ((chbyte & ACK) == ACK)) { 255 if (log.isDebugEnabled()) { 256 log.debug("{} Error packet stage 3 {}\n{}", this.getDisplayName(), Integer.toHexString(packet[i]), rc.toHexString()); 257 } 258 i++; 259 packetsinerror++; 260 return; 261 } 262 263 chbyte = (char) (((type & 0x03) << 6) | (chbyte & 0x3f)); 264 type = (char) ((type >> 2) & 0x0F); 265 266 switch (type) { 267 case 0: 268 log.debug("{} CV Value {}{}", this.getDisplayName(), (int) chbyte, rcTag); 269 cvvalue = chbyte; 270 if (rcTag != null) { 271 rcTag.setWhereLastSeen(this); 272 if (rcTag.getExpectedCv() != -1) { 273 rcTag.setCvValue(chbyte); 274 } else { 275 rcTag.setCV(rc.getCvNumber(), chbyte); 276 } 277 } 278 break; 279 case 4: 280 if (log.isDebugEnabled()) { 281 log.debug("{} Create/Get id tag for {}", this.getDisplayName(), rc.getDccAddress()); 282 } 283 addr = rc.getDccAddress(); 284 addr_type = rc.getAddressType(); 285 break; 286 case 1: // Address byte 1 287 log.debug("Address Byte 1"); 288 address_part_1 = (0x100 | chbyte); 289 addressp1found = 0; 290 break; 291 case 2: //Address byte 2 292 log.debug("{} Address part 2:", this.getDisplayName()); 293 address_part_2 = chbyte; 294 if (packetTypeCmd == 0x03) { 295 log.debug("Type three packet so shouldn't not pair part one with part two if it came from the previous packet"); 296 //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 297 //address_part_1 = 0x100; 298 // break; 299 } 300 if (!((address_part_1 & 0x100) == 0x100)) { 301 log.debug("{} Break at Address part 1, part one not complete", this.getDisplayName()); 302 break; 303 } 304 rcTag = decodeAddress(); 305 break; 306 case 3: //Actual Speed / load 307 if ((chbyte & 0x80) == 0x80) { 308 actual_speed = (chbyte & 0x7f); 309 log.debug("{} Actual Speed: {}", this.getDisplayName(), actual_speed); 310 } else { 311 actual_load = (chbyte & 0x7f); 312 log.debug("{} Actual Load: {}", this.getDisplayName(), actual_load); 313 } 314 if (rcTag != null) { 315 rcTag.setActualLoad(actual_load); 316 rcTag.setActualSpeed(actual_speed); 317 rcTag.setWhereLastSeen(this); 318 } 319 break; 320 case 5: //Routing number 321 routing_no = chbyte; 322 if (rcTag != null) { 323 rcTag.setRoutingNo(routing_no); 324 rcTag.setWhereLastSeen(this); 325 } 326 break; 327 case 6: //Location 328 location = chbyte; 329 if (rcTag != null) { 330 rcTag.setLocation(location); 331 rcTag.setWhereLastSeen(this); 332 } 333 break; 334 case 7: //Fuel water 335 if ((chbyte & 0x80) == 0x80) { 336 fuelLevel = (chbyte & 0x7f); 337 } else { 338 waterLevel = (chbyte & 0x7f); 339 } 340 341 if (rcTag != null) { 342 rcTag.setWaterLevel(waterLevel); 343 rcTag.setFuelLevel(fuelLevel); 344 rcTag.setWhereLastSeen(this); 345 } 346 break; 347 case 8: //Temp 348 if (!((chbyte & 0x80) == 0x80)) { 349 actual_temperature = (chbyte & 0x7F); 350 } 351 if (rcTag != null) { 352 rcTag.setActualTemperature(actual_temperature); 353 rcTag.setWhereLastSeen(this); 354 } 355 break; 356 case 15: //CV Address Value 357 log.debug("{} CV Address and value:", this.getDisplayName()); 358 i = i + 2; 359 //len = 4; 360 break; 361 default: 362 log.info("unknown railcom type packet {}", type); 363 break; 364 } 365 i++; 366 367 } 368 } 369 } 370 371 RailCom decodeAddress() { 372 RailCom rcTag; 373 log.debug("{} Create/Get id tag for {}", this.getDisplayName(), addr); 374 rcTag = (RailCom)jmri.InstanceManager.getDefault(jmri.RailComManager.class).provideIdTag("" + addr); 375 376 if ((fuelLevel != -1)) { 377 rcTag.setFuelLevel(fuelLevel); 378 } 379 if ((waterLevel != -1)) { 380 rcTag.setWaterLevel(waterLevel); 381 } 382 if ((routing_no != -1)) { 383 rcTag.setRoutingNo(routing_no); 384 } 385 if ((location != -1)) { 386 rcTag.setLocation(location); 387 } 388 if ((actual_temperature != -1)) { 389 rcTag.setActualTemperature(actual_temperature); 390 } 391 if ((actual_load != -1)) { 392 rcTag.setActualLoad(actual_load); 393 } 394 if ((actual_speed != -1)) { 395 rcTag.setActualSpeed(actual_speed); 396 } 397 for (Map.Entry<Integer, Integer> entry : cvValues.entrySet()) { 398 rcTag.setCV(entry.getKey(), entry.getValue()); 399 if (cvvalue != -1) { 400 rcTag.setCvValue(cvvalue); 401 } 402 } 403 404 address_part_1 = 0; 405 address_part_2 = -1; 406 notify(rcTag); 407 return rcTag; 408 } 409 410 RailCom provideTag(int address, int addr_type) { 411 log.debug("provide Tag"); 412 RailCom rcTag = (RailCom) jmri.InstanceManager.getDefault(jmri.RailComManager.class).provideIdTag("" + address); 413 notify(rcTag); 414 return rcTag; 415 } 416 417 public final static char ACK = 0x80; 418 public final static char ACK_1 = 0x81; 419 public final static char ACK_2 = 0x82; 420 public final static char ACK_3 = 0x83; 421 public final static char ACK_4 = 0x84; 422 public final static char ACK_5 = 0x85; 423 public final static char ACK_6 = 0x86; 424 public final static char ERROR = 0xFF; 425 426 private final static char[] decode = new char[]{ 427 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 428 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ACK_1, 429 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 0x33, 430 ERROR, ERROR, ERROR, 0x34, ERROR, 0x35, 0x36, ERROR, 431 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 0x3A, 432 ERROR, ERROR, ERROR, 0x3B, ERROR, 0x3C, 0x37, ERROR, 433 ERROR, ERROR, ERROR, 0x3F, ERROR, 0x3D, 0x38, ERROR, 434 ERROR, 0x3E, 0x39, ERROR, ACK_5, ERROR, ERROR, ERROR, 435 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 0x24, 436 ERROR, ERROR, ERROR, 0x23, ERROR, 0x22, 0x21, ERROR, 437 ERROR, ERROR, ERROR, 0x1F, ERROR, 0x1E, 0x20, ERROR, 438 ERROR, 0x1D, 0x1C, ERROR, 0x1B, ERROR, ERROR, ERROR, 439 ERROR, ERROR, ERROR, 0x19, ERROR, 0x18, 0x1A, ERROR, 440 ERROR, 0x17, 0x16, ERROR, 0x15, ERROR, ERROR, ERROR, 441 ERROR, 0x25, 0x14, ERROR, 0x13, ERROR, ERROR, ERROR, 442 0x32, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 443 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ACK_2, 444 ERROR, ERROR, ERROR, 0x0E, ERROR, 0x0D, 0x0C, ERROR, 445 ERROR, ERROR, ERROR, 0x0A, ERROR, 0x09, 0x0B, ERROR, 446 ERROR, 0x08, 0x07, ERROR, 0x06, ERROR, ERROR, ERROR, 447 ERROR, ERROR, ERROR, 0x04, ERROR, 0x03, 0x05, ERROR, 448 ERROR, 0x02, 0x01, ERROR, 0x00, ERROR, ERROR, ERROR, 449 ERROR, 0x0F, 0x10, ERROR, 0x11, ERROR, ERROR, ERROR, 450 0x12, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 451 ERROR, ERROR, ERROR, ACK_3, ERROR, 0x2B, 0x30, ERROR, 452 ERROR, 0x2A, 0x2F, ERROR, 0x31, ERROR, ERROR, ERROR, 453 ERROR, 0x29, 0x2E, ERROR, 0x2D, ERROR, ERROR, ERROR, 454 0x2C, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 455 ERROR, ACK_6, 0x28, ERROR, 0x27, ERROR, ERROR, ERROR, 456 0x26, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 457 ACK_4, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, 458 ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR, ERROR}; 459 460 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Dcc4PcReporter.class); 461 462}