001package jmri.jmrit.decoderdefn; 002 003import org.slf4j.Logger; 004import org.slf4j.LoggerFactory; 005 006/** 007 * Interact with a programmer to identify the 008 * {@link jmri.jmrit.decoderdefn.DecoderIndexFile} entry for a decoder on the 009 * programming track. Create a subclass of this which implements {@link #done} 010 * to handle the results of the identification. 011 * <p> 012 * This is a class (instead of a {@link jmri.jmrit.decoderdefn.DecoderIndexFile} 013 * member function) to simplify use of {@link jmri.Programmer} callbacks. 014 * <p> 015 * Contains manufacturer-specific code to generate a 3rd "productID" identifier, 016 * in addition to the manufacturer ID and model ID:<ul> 017 * <li>Dietz (mfgID == 115) CV128 is ID</li> 018 * <li>DIY: (mfgID == 13) CV47 is the highest byte, CV48 is high byte, CV49 is 019 * low byte, CV50 is the lowest byte; (CV47 == 1) is reserved for the Czech 020 * Republic</li> 021 * <li>Doehler & Haass: (mfgID == 97) CV261 is ID from 2020 firmwares</li> 022 * <li>ESU: (mfgID == 151, modelID == 255) use RailCom® Product ID CVs; 023 * write {@literal 0=>CV31}, write {@literal 255=>CV32}, then CVs 261 (lowest) 024 * to 264 (highest) are a four byte ID</li> 025 * <li>Harman: (mfgID == 98) CV112 is high byte, CV113 is low byte of ID</li> 026 * <li>Hornby: (mfgID == 48) <ul> 027 * <li>If CV7 = 254, this is a HN7000 series decoder. The ID is in 028 * CV200(MSB), CV201 (LSB) 029 * <li>Otherwise CV159 is the ID. If (CV159 == 143), CV159 is 030 * low byte of ID and CV158 is high byte of ID. C159 is not present in some 031 * models, in which case no "productID" can be determined. (This code uses 032 * {@link #setOptionalCv(boolean flag) setOptionalCv()} and 033 * {@link #isOptionalCv() isOptionalCv()} as documented below.)</li> 034 * </ul> 035 * <li>PIKO: (mfgID = 162) write {@literal 0=>CV31}, write {@literal 255=>CV32}, 036 * then read CV315, CV316, CV317 and do a decimal concatenation 037 * <li>QSI: (mfgID == 113) write {@literal 254=>CV49}, write {@literal 4=>CV50}, 038 * then CV56 is high byte, write {@literal 5=>CV50}, then CV56 is low byte of 039 * ID</li> 040 * <li>SoundTraxx: (mfgID == 141, modelID == 70, 71 or 72) The product ID is made from 041 * <ul> 042 * <li>CV 256 bits 0-7 043 * <li>CV 255 bits 8-10 044 * <li>CV 253 bit 11-18 045 * </ul> 046 * i.e. productID = CV256 | ((CV255 & 7) << 8) | (CV253 << 11) 047 * </li> 048 * <li>TCS: (mfgID == 153) CV249 is physical hardware id, V5 and above use 049 * CV248, CV110 and CV111 to identify specific sound sets and 050 * features. New productID process triggers if (CV249 > 128).</li> 051 * <li>Train-O-Matic: (mfgID == 78) CV508 lowest byte, 052 * CV509 low byte and CV510 high byte</li> 053 * <li>Zimo: (mfgID == 145) CV250 is ID</li> 054 * </ul> 055 * <dl> 056 * <dt>Optional CVs:</dt> 057 * <dd> 058 * Some decoders have CVs that may or may not be present. In this case: 059 * <ul> 060 * <li>Call {@link #setOptionalCv(boolean flag) setOptionalCv(true)} prior to 061 * the {@link #readCV(String cv) readCV(cv)} call.</li> 062 * <li>At the next step, check the returned value of 063 * {@link #isOptionalCv() isOptionalCv()}. If it is still {@code true}, the CV 064 * read failed (despite retries) and the contents of the {@code value} field are 065 * undefined. You can either:<br> 066 * <ul> 067 * <li>{@code return true} to indicate the Identify process has completed 068 * successfully without using the failed CV.</li> 069 * <li>Set up an alternate CV read/write procedure and {@code return false} to 070 * continue. Don't forget to call 071 * {@link #setOptionalCv(boolean flag) setOptionalCv(false)} if the next CV read 072 * is not intended to be optional.</li> 073 * </ul> 074 * </ul> 075 * </dd> 076 * </dl> 077 * <p> 078 * TODO: 079 * <br>The RailCom® Product ID is a 32 bit unsigned value. {@code productID} 080 * is currently {@code int} with -1 signifying a null value. Potential for value 081 * conflict exists but changing would involve significant code changes 082 * elsewhere. 083 * 084 * @author Bob Jacobsen Copyright (C) 2001, 2010 085 * @author Howard G. Penny Copyright (C) 2005 086 * @see jmri.jmrit.symbolicprog.CombinedLocoSelPane 087 * @see jmri.jmrit.symbolicprog.NewLocoSelPane 088 */ 089public abstract class IdentifyDecoder extends jmri.jmrit.AbstractIdentify { 090 091 public IdentifyDecoder(jmri.Programmer programmer) { 092 super(programmer); 093 } 094 095 Manufacturer mfgID = null; // cv8 096 int intMfg = -1; // needed to hold mfg number in cases that don't match enum 097 int modelID = -1; // cv7 098 int productIDhigh = -1; 099 int productIDlow = -1; 100 int productIDhighest = -1; 101 int productIDlowest = -1; 102 int productID = -1; 103 104 /** 105 * Represents specific CV8 values. We don't do 106 * product ID for anything not defined here. 107 */ 108 enum Manufacturer { 109 DIETZ(115), 110 DIY(13), 111 DOEHLER(97), 112 ESU(151), 113 HARMAN(98), 114 HORNBY(48), 115 PIKO(162), 116 QSI(113), 117 SOUNDTRAXX(141), 118 TCS(153), 119 TRAINOMATIC(78), 120 ZIMO(145); 121 122 public int value; 123 124 private Manufacturer(int value) { 125 this.value = value; 126 } 127 128 public static Manufacturer forValue(int value) { 129 for (var e : values()) { 130 if (e.value == value) { 131 return e; 132 } 133 } 134 return null; 135 } 136 } 137 138 // steps of the identification state machine 139 @Override 140 public boolean test1() { 141 // read cv8 142 statusUpdate("Read MFG ID - CV 8"); 143 readCV("8"); 144 return false; 145 } 146 147 @Override 148 public boolean test2(int value) { 149 mfgID = Manufacturer.forValue(value); 150 intMfg = value; 151 statusUpdate("Read MFG version - CV 7"); 152 readCV("7"); 153 return false; 154 } 155 156 @Override 157 public boolean test3(int value) { 158 modelID = value; 159 if (mfgID == null) return true; // done 160 switch (mfgID) { 161 case PIKO: 162 statusUpdate("Set PI for Read Product ID High Byte"); 163 writeCV("31", 0); 164 return false; 165 case QSI: 166 statusUpdate("Set PI for Read Product ID High Byte"); 167 writeCV("49", 254); 168 return false; 169 case TCS: 170 statusUpdate("Read decoder ID CV 249"); 171 readCV("249"); 172 return false; 173 case HORNBY: 174 if (modelID == 254) { // HN7000 175 statusUpdate("Read Product ID High Byte CV 200"); 176 177 readCV("200"); 178 return false; 179 } else { // other than HN7000 180 statusUpdate("Read optional decoder ID CV 159"); 181 setOptionalCv(true); 182 readCV("159"); 183 return false; 184 } 185 case ZIMO: 186 statusUpdate("Read decoder ID CV 250"); 187 readCV("250"); 188 return false; 189 case SOUNDTRAXX: 190 if (modelID >= 70 && modelID <= 72) { // SoundTraxx Econami, Tsunami2 and Blunami 191 statusUpdate("Read productID high CV253"); 192 readCV("253"); 193 return false; 194 } else return true; 195 case HARMAN: 196 statusUpdate("Read decoder ID high CV 112"); 197 readCV("112"); 198 return false; 199 case ESU: 200 if (modelID == 255) { // ESU recent 201 statusUpdate("Set PI for Read productID"); 202 writeCV("31", 0); 203 return false; 204 } else return true; 205 case DIY: 206 statusUpdate("Read decoder product ID #1 CV 47"); 207 readCV("47"); 208 return false; 209 case DOEHLER: 210 statusUpdate("Read optional decoder ID CV 261"); 211 setOptionalCv(true); 212 readCV("261"); 213 return false; 214 case TRAINOMATIC: 215 statusUpdate("Read productID #1 CV 510"); 216 readCV("510"); 217 return false; 218 case DIETZ: 219 statusUpdate("Read productID CV 128"); 220 readCV("128"); 221 return false; 222 default: 223 return true; 224 } 225 } 226 227 @Override 228 public boolean test4(int value) { 229 switch (mfgID) { 230 case PIKO: 231 statusUpdate("Set SI for Read Product ID High Byte"); 232 writeCV("32", 255); 233 return false; 234 case QSI: 235 statusUpdate("Set SI for Read Product ID High Byte"); 236 writeCV("50", 4); 237 return false; 238 case TCS: 239 if(value < 129){ //check for mobile decoders 240 productID = value; 241 return true; 242 } 243 else{ 244 productIDlowest = value; 245 statusUpdate("Read decoder sound version number"); 246 readCV("248"); 247 return false; 248 } 249 case HORNBY: 250 if (modelID == 254) { // HN7000 251 productIDhigh = value; 252 statusUpdate("ProductID High - " + productIDhigh + " - reading CV201"); 253 readCV("201"); 254 return false; 255 } else { // other than HN7000 256 if (isOptionalCv()) { 257 return true; 258 } 259 if (value == 143) { 260 productIDlow = value; 261 statusUpdate("Read Product ID High Byte"); 262 readCV("158"); 263 return false; 264 } else { 265 productID = value; 266 return true; 267 } 268 } 269 case ZIMO: 270 productID = value; 271 return true; 272 case SOUNDTRAXX: 273 if (modelID >= 70 && modelID <= 72) { // SoundTraxx Econami, Tsunami2 and Blunami 274 productIDhighest = value; 275 statusUpdate("Read decoder productID low CV256"); 276 readCV("256"); 277 return false; 278 } else return true; 279 case HARMAN: 280 productIDhigh = value; 281 statusUpdate("Read decoder ID low CV 113"); 282 readCV("113"); 283 return false; 284 case ESU: 285 statusUpdate("Set SI for Read productID"); 286 writeCV("32", 255); 287 return false; 288 case DIY: 289 productIDhighest = value; 290 statusUpdate("Read decoder product ID #2 CV 48"); 291 readCV("48"); 292 return false; 293 case DOEHLER: 294 if (isOptionalCv()) { 295 return true; 296 } 297 productID = value; 298 return true; 299 case TRAINOMATIC: 300 productIDhigh = value; 301 statusUpdate("Read productID #2 CV 509"); 302 readCV("509"); 303 return false; 304 case DIETZ: 305 productID = value; 306 return true; 307 default: 308 log.error("unexpected step 4 reached with value: {}", value); 309 return true; 310 } 311 } 312 313 @Override 314 public boolean test5(int value) { 315 switch (mfgID) { 316 case PIKO: 317 statusUpdate("Read Product ID Highest Byte"); 318 readCV("315"); 319 return false; 320 case QSI: 321 statusUpdate("Read Product ID High Byte"); 322 readCV("56"); 323 return false; 324 case HORNBY: 325 if (modelID == 254) { // HN7000 326 productIDlow = value; 327 productID = productIDlow + (productIDhigh * 256); 328 statusUpdate("ProductID is " + productID); 329 return true; 330 } else { // other than HN7000 331 productIDhigh = value; 332 productID = (productIDhigh << 8) | productIDlow; 333 return true; 334 } 335 case SOUNDTRAXX: 336 if (modelID >= 70 && modelID <= 72) { // SoundTraxx Econami, Tsunami2 and Blunami 337 productIDlow = value; 338 readCV("255"); 339 return false; 340 } else return true; 341 case HARMAN: 342 productIDlow = value; 343 productID = (productIDhigh << 8) | productIDlow; 344 return true; 345 case ESU: 346 statusUpdate("Read productID Byte 1"); 347 readCV("261"); 348 return false; 349 case DIY: 350 productIDhigh = value; 351 statusUpdate("Read decoder product ID #3 CV 49"); 352 readCV("49"); 353 return false; 354 case TCS: 355 productIDlow = value; 356 statusUpdate("Read decoder extended Version ID Low Byte"); 357 readCV("111"); 358 return false; 359 case TRAINOMATIC: 360 productIDlow = value; 361 statusUpdate("Read productID #3 CV 508"); 362 readCV("508"); 363 return false; 364 default: 365 log.error("unexpected step 5 reached with value: {}", value); 366 return true; 367 } 368 } 369 370 @Override 371 public boolean test6(int value) { 372 switch (mfgID) { 373 case PIKO: 374 productID = value; 375 statusUpdate("Read Product ID Middle Byte"); 376 readCV("316"); 377 return false; 378 case QSI: 379 productIDhigh = value; 380 statusUpdate("Set SI for Read Product ID Low Byte"); 381 writeCV("50", 5); 382 return false; 383 case HORNBY: 384 // HN7000 reaches here 385 productID = value + (productIDhigh * 256) + (productIDhighest * 256 * 256); 386 return true; 387 case ESU: 388 productID = value; 389 statusUpdate("Read productID Byte 2"); 390 readCV("262"); 391 return false; 392 case DIY: 393 productIDlow = value; 394 statusUpdate("Read decoder product ID #4 CV 50"); 395 readCV("50"); 396 return false; 397 case SOUNDTRAXX: 398 if (modelID >= 70 && modelID <= 72) { // SoundTraxx Econami, Tsunami2 and Blunami 399 productIDhigh = value; 400 productID = productIDlow | ((productIDhigh & 7) << 8) | (productIDhighest << 11); 401 return true; 402 } else return true; 403 case TCS: 404 productIDhigh = value; 405 statusUpdate("Read decoder extended Version ID High Byte"); 406 readCV("110"); 407 return false; 408 case TRAINOMATIC: 409 productID = value + (productIDlow * 256) + (productIDhigh * 256 * 256); 410 return true; 411 default: 412 log.error("unexpected step 6 reached with value: {}", value); 413 return true; 414 } 415 } 416 417 @Override 418 public boolean test7(int value) { 419 switch (mfgID) { 420 case PIKO: 421 productID = productID*100+value; 422 statusUpdate("Read Product ID Lowest Byte"); 423 readCV("317"); 424 return false; 425 case QSI: 426 statusUpdate("Read Product ID Low Byte"); 427 readCV("56"); 428 return false; 429 case ESU: 430 productID = productID + (value * 256); 431 statusUpdate("Read productID Byte 3"); 432 readCV("263"); 433 return false; 434 case DIY: 435 productIDlowest = value; 436 productID = (((((productIDhighest << 8) | productIDhigh) << 8) | productIDlow) << 8) | productIDlowest; 437 return true; 438 case TCS: 439 productIDhighest = value; 440 if (((productIDlowest >= 129 && productIDlowest <= 135) && (productIDlow == 5))||(modelID >= 5)){ 441 if ((productIDlowest == 180) && (modelID == 5)) { 442 productID = productIDlowest+(productIDlow*256); 443 } else { 444 productID = productIDlowest+(productIDlow*256)+(productIDhigh*256*256)+(productIDhighest*256*256*256); 445 } 446 } else if ((((productIDlowest >= 129 && productIDlowest <= 135) || (productIDlowest >= 170 && productIDlowest <= 172) || productIDlowest == 180) && (modelID == 4))) { 447 productID = productIDlowest+(productIDlow*256); 448 } else { 449 productID = productIDlowest; 450 } 451 return true; 452 default: 453 log.error("unexpected step 7 reached with value: {}", value); 454 return true; 455 } 456 } 457 458 @Override 459 public boolean test8(int value) { 460 switch (mfgID) { 461 case PIKO: 462 productID = productID*100+value; 463 return true; 464 case QSI: 465 productIDlow = value; 466 productID = (productIDhigh * 256) + productIDlow; 467 return true; 468 case ESU: 469 productID = productID + (value * 256 * 256); 470 statusUpdate("Read productID Byte 4"); 471 readCV("264"); 472 return false; 473 default: 474 log.error("unexpected step 8 reached with value: {}", value); 475 return true; 476 } 477 } 478 479 @Override 480 public boolean test9(int value) { 481 if (mfgID == Manufacturer.ESU) { 482 productID = productID + (value * 256 * 256 * 256); 483 return true; 484 } 485 log.error("unexpected step 9 reached with value: {}", value); 486 return true; 487 } 488 489 @Override 490 protected void statusUpdate(String s) { 491 message(s); 492 if (s.equals("Done")) { 493 done(intMfg, modelID, productID); 494 log.info("Decoder returns mfgID:{};modelID:{};productID:{}", intMfg, modelID, productID); 495 } else if (log.isDebugEnabled()) { 496 log.debug("received status: {}", s); 497 } 498 } 499 500 /** 501 * Indicate when identification is complete. 502 * 503 * @param mfgID identified manufacturer identity 504 * @param modelID identified model identity 505 * @param productID identified product identity 506 */ 507 protected abstract void done(int mfgID, int modelID, int productID); 508 509 /** 510 * Provide a user-readable message about progress. 511 * 512 * @param m the message to provide 513 */ 514 protected abstract void message(String m); 515 516 // initialize logging 517 private final static Logger log = LoggerFactory.getLogger(IdentifyDecoder.class); 518 519}