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 *         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 &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 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}