001package jmri.jmrix.nce;
002
003import jmri.jmrix.ConnectionStatus;
004import jmri.util.swing.JmriJOptionPane;
005
006/**
007 * Continuously checks and confirms that the communication link to the NCE
008 * Command Station is operational by reading the revision number of the EPROM.
009 * Only invokes the EPROM read when the interface experiences a timeout.
010 * <p>
011 * Checks revision of NCE CS by reading the 3 byte revision. Sends a warning
012 * message NCE EPROM found and preferences are not correct for revision selected.
013 * <p>
014 * Also checks for March 2007 EPROM and warns user about Monitoring feedback.
015 *
016 * Confirms connection to PowerCab by issuing dummy loco command.
017 *
018 * @author Daniel Boudreau (C) 2007, 2010, 2012, 2021
019 * @author Ken Cameron (C) 2023
020 *
021 */
022public class NceConnectionStatus implements NceListener {
023
024    private static final boolean JOptPane_ERROR_MESSAGES_ENABLED = true;
025    private static final boolean JOptPane_WARNING_MESSAGES_ENABLED = false; // Disabled for headless operations!
026
027    // EPROM Checker states
028    private static final int INIT_STATE = 0; // Initial state
029    private static final int WAIT_STATE = 1; // Waiting for reply
030    private static final int CHECK_STATE = 2; // Confirm connection
031    private static final int CHECK_OK = 3; // Valid response
032    private static final int NORMAL_STATE = 4; // Normal state
033
034    private static final int WARN1_STATE = 8; // Serial interface is not functioning properly
035    private static final int WARN2_STATE = 9; // Detected 2007 March EPROM
036
037    // all of the error states below display a JmriJOptionPane error message
038    private static final int ERROR1_STATE = 16; // Wrong revision EPROM, 2004 or earlier
039    private static final int ERROR2_STATE = 17; // Wrong revision EPROM, 2006 or later
040    private static final int ERROR4_STATE = 19; // Wrong NCE System
041    private static final int ERROR5_STATE = 20; // Wrong NCE System, detected Power Cab
042    private static final int ERROR6_STATE = 21; // Wrong NCE System, detected Smart Booster SB3
043    private static final int ERROR7_STATE = 22; // Wrong NCE System, detected Power Pro
044    private static final int ERROR8_STATE = 23; // Wrong NCE System, detected SB5
045    private static final int ERROR9_STATE = 24; // Wrong NCE System, detected PH5
046
047    private int epromState = INIT_STATE; // EPROM state
048    private boolean epromChecked = false;
049
050    // Our current knowledge of NCE Command Station EPROMs.
051    // The ones we don't use (hence haven't really confirmed) are
052    // commented out to preserve the value, but indicate we don't use them.
053    private static final int VV_1999 = 4; // Revision of Apr 1999 EPROM VV.MM.mm = 4.0.1
054    // private static final int MM_1999 = 0;
055    // private static final int mm_1999 = 1;
056
057    private static final int VV_2004 = 6; // Revision of Dec 2004 EPROM VV.MM.mm = 6.0.0
058    private static final int MM_2004 = 0;
059    // private static final int mm_2004 = 0;
060
061    private static final int VV_2007 = 6; // Revision of Mar 2007 EPROM VV.MM.mm = 6.2.0
062    private static final int MM_2007 = 2;
063    private static final int mm_2007 = 0;
064
065    // private static final int mm_2007a = 1; // Revision of May 2007 EPROM VV.MM.mm = 6.2.1
066    // private static final int mm_2008 = 2; // Revision of 2008 EPROM VV.MM.mm = 6.2.2
067    private static final int mm_2021 = 3; // Revision of 2021 EPROM VV.MM.mm = 6.2.3
068
069    private static final int VV_2012 = 7; // Revision 2012 EPROM VV.MM.mm = 7.2.0
070    private static final int MM_2012 = 2;
071    
072    // PH5 details, 2023
073    private static final int VV_PH5 = 8;    // 1st Edition
074    private static final int MM_PH5 = 0;
075    
076    // USB -> Cab bus adapter:
077    // When used with PowerCab V1.28 - 6.3.0
078    // When used with SB3 V1.28 - 6.3.1 (No program track on an SB3)
079    // When used with PH-Pro or PH-10 - 6.3.2 (limited set of features available
080    // through cab bus)
081    //
082    // Future version of PowerCab V1.61 - 6.3.4
083    // Future version of SB3 V1.61 - 6.3.5
084    //
085    // NOTE: The USB port can not read CS memory, unless greater than 7.* version
086    private static final int VV_USB_V6 = 6; // Revision of USB EPROM VV.MM.mm = 6.3.x
087    private static final int VV_USB_V7 = 7; // 2012 revision of USB EPROM VV.MM.mm = 7.3.x
088    private static final int MM_USB = 3;
089    // V6 flavors
090    private static final int mm_USB_V6_PwrCab = 0; // PowerCab
091    private static final int mm_USB_V6_SB3 = 1; // SB3
092    private static final int mm_USB_V6_PH = 2; // PH-Pro or PH-10
093    // Future releases by NCE (Not used by JMRI yet!)
094    // private static final int mm_USB_V6_ALL = 3; // All systems, not currently used
095    // private static final int mm_USB_V6_PC161 = 4; // Future use, PowerCab 1.61, not currently used
096    // private static final int mm_USB_V6_SB161 = 5; // Future use, SB3 1.61, not currently used
097    // V7 flavors
098    private static final int mm_USB_V7_PC_128_A = 0; // PowerCab with 1.28c
099    private static final int mm_USB_V7_SB5_165_A = 1; // SB5 with 1.65
100    private static final int mm_USB_V7_SB5_165_B = 2; // SB5 with 1.65
101    // private static final int mm_USB_V7_PC_165 = 3; // PowerCab with 1.65
102    private static final int mm_USB_V7_PC_128_B = 4; // PowerCab with 1.28c
103    private static final int mm_USB_V7_SB3 = 5; // SB3 with 1.28c
104    private static final int mm_USB_V7_PH = 6; // PowerPro with 3.1.2007
105    // private static final int mm_USB_V7_ALL = 7; // All systems
106    
107
108    private NceTrafficController tc = null;
109
110    public NceConnectionStatus(NceTrafficController tc) {
111        super();
112        this.tc = tc;
113    }
114
115    public NceMessage nceEpromPoll() {
116
117        if (tc.getCommandOptions() <= NceTrafficController.OPTION_1999) {
118            return null;
119        }
120
121        // normal state for this routine
122        if (epromState == NORMAL_STATE) {
123            // are there interface timeouts?
124            if (tc.hasTimeouts()) {
125                epromState = INIT_STATE;
126            } else {
127                return null;
128            }
129        }
130
131        // determine if really connected to command station by issuing dummy locomotive
132        // command, to short address 0.
133        if (epromState == CHECK_STATE) {
134            if (tc.getCommandOptions() > NceTrafficController.OPTION_2004) {
135                return NceMessage.sendLocoCmd(tc, 0x0000, NceMessage.LOCO_CMD_SELECT_LOCO, (byte) 00);
136            }
137            epromState = CHECK_OK;
138        }
139
140        if (epromState == CHECK_OK) {
141            ConnectionStatus.instance().setConnectionState(tc.getUserName(), tc.getPortName(),
142                    ConnectionStatus.CONNECTION_UP);
143            epromState = NORMAL_STATE;
144            return null;
145        }
146
147        if (epromState != INIT_STATE) {
148            ConnectionStatus.instance().setConnectionState(tc.getUserName(), tc.getPortName(),
149                    ConnectionStatus.CONNECTION_DOWN);
150        }
151
152        // no response from command station?
153        if (epromState == WAIT_STATE) {
154            log.warn("{}: Incorrect or no response from NCE command station port: {}", tc.getUserName(), tc.getPortName());
155            if (JOptPane_WARNING_MESSAGES_ENABLED) {
156                JmriJOptionPane.showMessageDialog(null,
157                        "JMRI could not establish communication with NCE command station. \n" +
158                                "Check the \"Serial port:\" and \"Baud rate:\" in Edit -> Preferences. \n" +
159                                "Confirm cabling and that the NCE system is powered up.",
160                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
161            }
162            epromState = WARN1_STATE;
163        }
164
165        // still no response from command station?
166        else if (epromState == WARN1_STATE) {
167            log.warn("{}: No response from NCE command station port: {}", tc.getUserName(), tc.getPortName());
168        }
169
170        if (epromState == ERROR1_STATE) {
171            if (JOptPane_ERROR_MESSAGES_ENABLED) {
172                JmriJOptionPane.showMessageDialog(null,
173                        "Wrong revision of Command Station EPROM selected in Preferences \n" +
174                                "Change the Command Station EPROM selection to \"2004 or earlier\"",
175                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
176            }
177            epromState = NORMAL_STATE;
178            return null;
179        }
180
181        if (epromState == ERROR2_STATE) {
182            if (JOptPane_ERROR_MESSAGES_ENABLED) {
183                JmriJOptionPane.showMessageDialog(null,
184                        "Wrong revision of Command Station EPROM selected in Preferences \n" +
185                                "Change the Command Station EPROM selection to \"2006 or later\"",
186                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
187            }
188            epromState = NORMAL_STATE;
189            return null;
190        }
191
192        if (epromState == WARN2_STATE) {
193            log.warn("{}: Detected 2007 March EPROM which doesn't provide reliable MONITORING feedback for turnouts", tc.getUserName());
194            // Need to add checkbox "Do not show this message again" otherwise
195            // the message can be a pain.
196            if (JOptPane_WARNING_MESSAGES_ENABLED) {
197                JmriJOptionPane.showMessageDialog(null,
198                        "The 2007 March EPROM doesn't provide reliable feedback," +
199                                " contact NCE if you want to use MONITORING feedback ",
200                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.INFORMATION_MESSAGE);
201            }
202            ConnectionStatus.instance().setConnectionState(tc.getUserName(), tc.getPortName(),
203                    ConnectionStatus.CONNECTION_UP);
204            epromState = NORMAL_STATE;
205            return null;
206        }
207
208        if (epromState == ERROR4_STATE) {
209            if (JOptPane_ERROR_MESSAGES_ENABLED) {
210                JmriJOptionPane.showMessageDialog(null,
211                        "Wrong NCE System Connection selected in Preferences. " +
212                                "Change the System Connection to \"" +
213                                jmri.jmrix.nce.serialdriver.ConnectionConfig.NAME +
214                                "\" or \"" +
215                                jmri.jmrix.nce.networkdriver.ConnectionConfig.NAME +
216                                "\".",
217                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
218            }
219            epromState = NORMAL_STATE;
220            return null;
221        }
222
223        if (epromState == ERROR5_STATE) {
224            if (JOptPane_ERROR_MESSAGES_ENABLED) {
225                JmriJOptionPane.showMessageDialog(null,
226                        "Wrong NCE System Connection selected in Preferences. " +
227                                "The System Connection \"" +
228                                jmri.jmrix.nce.usbdriver.ConnectionConfig.NAME +
229                                "\" should change the system to \"Power Cab\".",
230                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
231            }
232            epromState = NORMAL_STATE;
233            return null;
234        }
235
236        if (epromState == ERROR6_STATE) {
237            if (JOptPane_ERROR_MESSAGES_ENABLED) {
238                JmriJOptionPane.showMessageDialog(null,
239                        "Wrong NCE System Connection selected in Preferences. " +
240                                "The System Connection \"" +
241                                jmri.jmrix.nce.usbdriver.ConnectionConfig.NAME +
242                                "\" should change the system to \"Smart Booster SB3\".",
243                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
244            }
245            epromState = NORMAL_STATE;
246            return null;
247        }
248
249        if (epromState == ERROR7_STATE) {
250            if (JOptPane_ERROR_MESSAGES_ENABLED) {
251                JmriJOptionPane.showMessageDialog(null,
252                        "Wrong NCE System Connection selected in Preferences. " +
253                                "The System Connection \"" +
254                                jmri.jmrix.nce.usbdriver.ConnectionConfig.NAME +
255                                "\" should change the system to \"Power Pro\".",
256                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
257            }
258            epromState = NORMAL_STATE;
259            return null;
260        }
261
262        if (epromState == ERROR8_STATE) {
263            if (JOptPane_ERROR_MESSAGES_ENABLED) {
264                JmriJOptionPane.showMessageDialog(null,
265                        "Wrong NCE System Connection selected in Preferences. " +
266                                "The System Connection \"" +
267                                jmri.jmrix.nce.usbdriver.ConnectionConfig.NAME +
268                                "\" should change the system to \"SB5\".",
269                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
270            }
271            epromState = NORMAL_STATE;
272            return null;
273        }
274
275        // stay in warn state until reply
276        if (epromState != WARN1_STATE) {
277            epromState = WAIT_STATE;
278        }
279        // go ahead and read the EPROM revision
280        return NceMessage.getEpromVersion(tc);
281    }
282
283    @Override
284    public void message(NceMessage m) {
285        if (log.isDebugEnabled()) {
286            log.debug("{}: unexpected message", tc.getUserName());
287        }
288    }
289
290    @Override
291    public void reply(NceReply r) {
292        if (r.getNumDataElements() == NceMessage.REPLY_1 && epromState == CHECK_STATE) {
293            if (r.getElement(0) == NceMessage.NCE_OKAY) {
294                log.info("{}: Connected to NCE command station", tc.getUserName());
295                epromState = CHECK_OK;
296            } else {
297                log.warn("{}: Not connected to NCE command station", tc.getUserName());
298                epromState = INIT_STATE;
299            }
300        } else if (r.getNumDataElements() == NceMessage.REPLY_3) {
301
302            byte VV = (byte) r.getElement(0);
303            byte MM = (byte) r.getElement(1);
304            byte mm = (byte) r.getElement(2);
305            tc.setPwrProVers(VV, MM, mm);
306
307            // Is the reply valid? Check major revision, there are only three valid
308            // responses
309            // note that VV_2004 = VV_2007 = VV_USB
310            if (VV != VV_PH5 && VV != VV_2012 && VV != VV_2004 && VV != VV_1999) {
311                log.error("{}: Wrong major revision: {}", tc.getUserName(), Integer.toHexString(VV & 0xFF));
312                // show the entire revision number
313                log.info("{}: NCE EPROM revision = {}", tc.getUserName(), tc.getPwrProVersHexText());
314                return;
315            }
316
317            // We got a valid reply, now check to see if connected to command station
318            // or PowerCab
319            epromState = CHECK_STATE;
320
321            // Have we already done the error checking?
322            if (!epromChecked) {
323                checkEPROM(VV, MM, mm);
324                epromChecked = true;
325            }
326        } else {
327            log.warn("{}: wrong number of read bytes for revision check", tc.getUserName());
328        }
329    }
330
331    /**
332     * EPROM version check is only done once at startup
333     *
334     * @param VV Major version number
335     * @param MM Middle version number
336     * @param mm Minor version number
337     */
338    private void checkEPROM(byte VV, byte MM, byte mm) {
339        // Send to log file the NCE EPROM revision
340        log.info("{}: NCE EPROM revision = {}", tc.getUserName(), tc.getPwrProVersHexText());
341
342        // Warn about the March 2007 CS EPROM
343        if (VV == VV_2007 && MM == MM_2007 && mm == mm_2007) {
344            tc.setNceEpromMarch2007(true);
345            epromState = WARN2_STATE;
346        }
347
348        // check for Power Pro 2021 or later
349        if (VV == VV_2007 && MM == MM_2007 && mm >= mm_2021) {
350            tc.setPwrProVer060203orLater(true);
351        }
352
353        // Confirm that user selected correct revision of EPROM, check for old EPROM
354        // installed, new EPROM
355        // preferences
356        if ((VV <= VV_2007 && MM < MM_2007) && (tc.getCommandOptions() >= NceTrafficController.OPTION_2006)) {
357            log.error("{}: Wrong revision ({}) of the NCE Command Station EPROM selected in Preferences",
358                   tc.getUserName(), tc.getPwrProVersHexText());
359            epromState = ERROR1_STATE;
360        }
361
362        // Confirm that user selected correct revision of EPROM, check for new EPROM
363        // installed, old EPROM
364        // preferences
365        boolean eprom2007orNewer = ((VV == VV_2007) && (MM >= MM_2007));
366        if (((VV > VV_2007) || eprom2007orNewer) && (tc.getCommandOptions() < NceTrafficController.OPTION_2006)) {
367            log.error("{}: Wrong revision ({}) of the NCE Command Station EPROM selected in Preferences",
368                    tc.getUserName(), tc.getPwrProVersHexText());
369            epromState = ERROR2_STATE;
370        }
371
372        // Check that layout connection is correct
373        // PowerPro? 4 cases for PH, 1999, 2004, 2007, & 2012
374        if (VV == VV_1999 ||
375                (VV == VV_2004 && MM == MM_2004) ||
376                (VV == VV_2007 && MM == MM_2007) ||
377                (VV == VV_2012 && MM == MM_2012)) {
378            // make sure system connection is not NCE USB
379            if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
380                log.error("{}: System Connection is incorrect, detected Power Pro", tc.getUserName());
381                epromState = ERROR4_STATE;
382            }
383        }
384
385        // Check for USB 6.3.x
386        if (VV == VV_USB_V6 && MM == MM_USB) {
387            // USB detected, check to see if user preferences are correct
388            if (mm == mm_USB_V6_PwrCab && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_POWERCAB) {
389                log.error("{}: System Connection is incorrect, detected USB connected to a PowerCab",  tc.getUserName());
390                epromState = ERROR5_STATE;
391            }
392            if (mm == mm_USB_V6_SB3 && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_SB3) {
393                log.error("{}: System Connection is incorrect, detected USB connected to a Smart Booster SB3", tc.getUserName());
394                epromState = ERROR6_STATE;
395            }
396            if (mm == mm_USB_V6_PH && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_POWERPRO) {
397                log.error("{}: System Connection is incorrect, detected USB connected to a Power Pro", tc.getUserName());
398                epromState = ERROR7_STATE;
399            }
400        }
401        // Check for USB 7.3.x
402        if (VV == VV_USB_V7 && MM == MM_USB) {
403            // USB V7 detected, check to see if user preferences are correct
404            if (((mm == mm_USB_V7_PC_128_A) || (mm == mm_USB_V7_PC_128_B)) &&
405                    tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_POWERCAB) {
406                log.error("{}: System Connection is incorrect, detected USB connected to a PowerCab",  tc.getUserName());
407                epromState = ERROR5_STATE;
408            }
409            if (mm == mm_USB_V7_SB3 && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_SB3) {
410                log.error("{}: System Connection is incorrect, detected USB connected to a Smart Booster SB3", tc.getUserName());
411                epromState = ERROR6_STATE;
412            }
413            if (mm == mm_USB_V7_PH && tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_POWERPRO) {
414                log.error("{}: System Connection is incorrect, detected USB connected to a Power Pro", tc.getUserName());
415                epromState = ERROR7_STATE;
416            }
417            if (((mm == mm_USB_V7_SB5_165_A) || (mm == mm_USB_V7_SB5_165_B)) &&
418                    tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_SB5) {
419                log.error("{}: System Connection is incorrect, detected USB connected to a Smart Booster SB5", tc.getUserName());
420                epromState = ERROR8_STATE;
421            }
422        }
423        // check for PH5 not on PH5 connection
424        if ((VV == VV_PH5) && (MM == MM_PH5)) {
425            if (tc.getCommandOptions() != NceTrafficController.OPTION_PH5) {
426                log.error("{}: System Connection is incorrect, detected PH5 not connected as a PH5", tc.getUserName());
427                epromState = ERROR9_STATE;
428            }
429        }
430        // check for PH5 connection to a non-PH5 command station
431        if (tc.getCommandOptions() == NceTrafficController.OPTION_PH5) {
432            if ((VV != VV_PH5) || (MM != MM_PH5)) {
433                log.error("{}: System Connection is incorrect, detected something other than a PH5", tc.getUserName());
434                epromState = ERROR4_STATE;
435            }
436        }
437    }
438
439    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceConnectionStatus.class);
440
441}