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>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 &amp; 7) &lt;&lt; 8) | (CV253 &lt;&lt; 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 &gt; 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&reg; 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}