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