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 &amp; Haass: (mfgID == 97) CV261 is ID from 2020 firmwares</li>
022 * <li>ESU: (mfgID == 151, modelID == 255) use RailCom&reg; 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 &amp; 7) &lt;&lt; 8) | (CV253 &lt;&lt; 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 &gt; 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&reg; 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}