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 * CV47(MSB), CV48, CV49 (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 decoder ID CV 47"); 169 readCV("47"); 170 return false; 171 } else { // other than HN7000 172 statusUpdate("Read optional decoder ID CV 159"); 173 setOptionalCv(true); 174 readCV("159"); 175 return false; 176 } 177 case ZIMO: 178 statusUpdate("Read decoder ID CV 250"); 179 readCV("250"); 180 return false; 181 case SOUNDTRAXX: 182 if (modelID >= 70 && modelID <= 72) { // SoundTraxx Econami, Tsunami2 and Blunami 183 statusUpdate("Read productID high CV253"); 184 readCV("253"); 185 return false; 186 } else return true; 187 case HARMAN: 188 statusUpdate("Read decoder ID high CV 112"); 189 readCV("112"); 190 return false; 191 case ESU: 192 if (modelID == 255) { // ESU recent 193 statusUpdate("Set PI for Read productID"); 194 writeCV("31", 0); 195 return false; 196 } else return true; 197 case DIY: 198 statusUpdate("Read decoder product ID #1 CV 47"); 199 readCV("47"); 200 return false; 201 case DOEHLER: 202 statusUpdate("Read optional decoder ID CV 261"); 203 setOptionalCv(true); 204 readCV("261"); 205 return false; 206 case TRAINOMATIC: 207 statusUpdate("Read productID #1 CV 510"); 208 readCV("510"); 209 return false; 210 case DIETZ: 211 statusUpdate("Read productID CV 128"); 212 readCV("128"); 213 return false; 214 default: 215 return true; 216 } 217 } 218 219 @Override 220 public boolean test4(int value) { 221 switch (mfgID) { 222 case QSI: 223 statusUpdate("Set SI for Read Product ID High Byte"); 224 writeCV("50", 4); 225 return false; 226 case TCS: 227 if(value < 129){ //check for mobile decoders 228 productID = value; 229 return true; 230 } 231 else{ 232 productIDlowest = value; 233 statusUpdate("Read decoder sound version number"); 234 readCV("248"); 235 return false; 236 } 237 case HORNBY: 238 if (modelID == 254) { // HN7000 239 productIDhighest = value; 240 statusUpdate("Read decoder ID CV 48"); 241 readCV("48"); 242 return false; 243 } else { // other than HN7000 244 if (isOptionalCv()) { 245 return true; 246 } 247 if (value == 143) { 248 productIDlow = value; 249 statusUpdate("Read Product ID High Byte"); 250 readCV("158"); 251 return false; 252 } else { 253 productID = value; 254 return true; 255 } 256 } 257 case ZIMO: 258 productID = value; 259 return true; 260 case SOUNDTRAXX: 261 if (modelID >= 70 && modelID <= 72) { // SoundTraxx Econami, Tsunami2 and Blunami 262 productIDhighest = value; 263 statusUpdate("Read decoder productID low CV256"); 264 readCV("256"); 265 return false; 266 } else return true; 267 case HARMAN: 268 productIDhigh = value; 269 statusUpdate("Read decoder ID low CV 113"); 270 readCV("113"); 271 return false; 272 case ESU: 273 statusUpdate("Set SI for Read productID"); 274 writeCV("32", 255); 275 return false; 276 case DIY: 277 productIDhighest = value; 278 statusUpdate("Read decoder product ID #2 CV 48"); 279 readCV("48"); 280 return false; 281 case DOEHLER: 282 if (isOptionalCv()) { 283 return true; 284 } 285 productID = value; 286 return true; 287 case TRAINOMATIC: 288 productIDhigh = value; 289 statusUpdate("Read productID #2 CV 509"); 290 readCV("509"); 291 return false; 292 case DIETZ: 293 productID = value; 294 return true; 295 default: 296 log.error("unexpected step 4 reached with value: {}", value); 297 return true; 298 } 299 } 300 301 @Override 302 public boolean test5(int value) { 303 switch (mfgID) { 304 case QSI: 305 statusUpdate("Read Product ID High Byte"); 306 readCV("56"); 307 return false; 308 case HORNBY: 309 if (modelID == 254) { // HN7000 310 productIDhigh = value; 311 statusUpdate("Read decoder ID CV 49"); 312 readCV("49"); 313 return false; 314 } else { // other than HN7000 315 productIDhigh = value; 316 productID = (productIDhigh << 8) | productIDlow; 317 return true; 318 } 319 case SOUNDTRAXX: 320 if (modelID >= 70 && modelID <= 72) { // SoundTraxx Econami, Tsunami2 and Blunami 321 productIDlow = value; 322 readCV("255"); 323 return false; 324 } else return true; 325 case HARMAN: 326 productIDlow = value; 327 productID = (productIDhigh << 8) | productIDlow; 328 return true; 329 case ESU: 330 statusUpdate("Read productID Byte 1"); 331 readCV("261"); 332 return false; 333 case DIY: 334 productIDhigh = value; 335 statusUpdate("Read decoder product ID #3 CV 49"); 336 readCV("49"); 337 return false; 338 case TCS: 339 productIDlow = value; 340 statusUpdate("Read decoder extended Version ID Low Byte"); 341 readCV("111"); 342 return false; 343 case TRAINOMATIC: 344 productIDlow = value; 345 statusUpdate("Read productID #3 CV 508"); 346 readCV("508"); 347 return false; 348 default: 349 log.error("unexpected step 5 reached with value: {}", value); 350 return true; 351 } 352 } 353 354 @Override 355 public boolean test6(int value) { 356 switch (mfgID) { 357 case QSI: 358 productIDhigh = value; 359 statusUpdate("Set SI for Read Product ID Low Byte"); 360 writeCV("50", 5); 361 return false; 362 case HORNBY: 363 // HN7000 reaches here 364 productID = value + (productIDhigh * 256) + (productIDhighest * 256 * 256); 365 return true; 366 case ESU: 367 productID = value; 368 statusUpdate("Read productID Byte 2"); 369 readCV("262"); 370 return false; 371 case DIY: 372 productIDlow = value; 373 statusUpdate("Read decoder product ID #4 CV 50"); 374 readCV("50"); 375 return false; 376 case SOUNDTRAXX: 377 if (modelID >= 70 && modelID <= 72) { // SoundTraxx Econami, Tsunami2 and Blunami 378 productIDhigh = value; 379 productID = productIDlow | ((productIDhigh & 7) << 8) | (productIDhighest << 11); 380 return true; 381 } else return true; 382 case TCS: 383 productIDhigh = value; 384 statusUpdate("Read decoder extended Version ID High Byte"); 385 readCV("110"); 386 return false; 387 case TRAINOMATIC: 388 productID = value + (productIDlow * 256) + (productIDhigh * 256 * 256); 389 return true; 390 default: 391 log.error("unexpected step 6 reached with value: {}", value); 392 return true; 393 } 394 } 395 396 @Override 397 public boolean test7(int value) { 398 switch (mfgID) { 399 case QSI: 400 statusUpdate("Read Product ID Low Byte"); 401 readCV("56"); 402 return false; 403 case ESU: 404 productID = productID + (value * 256); 405 statusUpdate("Read productID Byte 3"); 406 readCV("263"); 407 return false; 408 case DIY: 409 productIDlowest = value; 410 productID = (((((productIDhighest << 8) | productIDhigh) << 8) | productIDlow) << 8) | productIDlowest; 411 return true; 412 case TCS: 413 productIDhighest = value; 414 if (((productIDlowest >= 129 && productIDlowest <= 135) && (productIDlow == 5))||(modelID >= 5)){ 415 if ((productIDlowest == 180) && (modelID == 5)) { 416 productID = productIDlowest+(productIDlow*256); 417 } else { 418 productID = productIDlowest+(productIDlow*256)+(productIDhigh*256*256)+(productIDhighest*256*256*256); 419 } 420 } else if ((((productIDlowest >= 129 && productIDlowest <= 135) || (productIDlowest >= 170 && productIDlowest <= 172) || productIDlowest == 180) && (modelID == 4))) { 421 productID = productIDlowest+(productIDlow*256); 422 } else { 423 productID = productIDlowest; 424 } 425 return true; 426 default: 427 log.error("unexpected step 7 reached with value: {}", value); 428 return true; 429 } 430 } 431 432 @Override 433 public boolean test8(int value) { 434 switch (mfgID) { 435 case QSI: 436 productIDlow = value; 437 productID = (productIDhigh * 256) + productIDlow; 438 return true; 439 case ESU: 440 productID = productID + (value * 256 * 256); 441 statusUpdate("Read productID Byte 4"); 442 readCV("264"); 443 return false; 444 default: 445 log.error("unexpected step 8 reached with value: {}", value); 446 return true; 447 } 448 } 449 450 @Override 451 public boolean test9(int value) { 452 if (mfgID == Manufacturer.ESU) { 453 productID = productID + (value * 256 * 256 * 256); 454 return true; 455 } 456 log.error("unexpected step 9 reached with value: {}", value); 457 return true; 458 } 459 460 @Override 461 protected void statusUpdate(String s) { 462 message(s); 463 if (s.equals("Done")) { 464 done(intMfg, modelID, productID); 465 log.info("Decoder returns mfgID:{};modelID:{};productID:{}", intMfg, modelID, productID); 466 } else if (log.isDebugEnabled()) { 467 log.debug("received status: {}", s); 468 } 469 } 470 471 /** 472 * Indicate when identification is complete. 473 * 474 * @param mfgID identified manufacturer identity 475 * @param modelID identified model identity 476 * @param productID identified product identity 477 */ 478 protected abstract void done(int mfgID, int modelID, int productID); 479 480 /** 481 * Provide a user-readable message about progress. 482 * 483 * @param m the message to provide 484 */ 485 protected abstract void message(String m); 486 487 // initialize logging 488 private final static Logger log = LoggerFactory.getLogger(IdentifyDecoder.class); 489 490}