001package jmri.jmrix.loconet;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import java.util.ArrayList;
006import java.util.List;
007import javax.annotation.Nonnull;
008import jmri.AddressedProgrammer;
009import jmri.ProgListener;
010import jmri.Programmer;
011import jmri.ProgrammerException;
012import jmri.ProgrammingMode;
013import jmri.beans.PropertyChangeSupport;
014import jmri.jmrix.loconet.hexfile.HexFileFrame;
015import jmri.jmrix.loconet.lnsvf2.LnSv2MessageContents;
016//import jmri.jmrix.loconet.swing.lncvprog.LncvProgPane;
017import jmri.jmrix.loconet.uhlenbrock.LncvMessageContents;
018
019import static jmri.jmrix.loconet.uhlenbrock.LncvMessageContents.createCvReadRequest;
020import static jmri.jmrix.loconet.uhlenbrock.LncvMessageContents.createCvWriteRequest;
021
022/**
023 * Provide an Ops Mode Programmer via a wrapper that works with the LocoNet
024 * SlotManager object.
025 * Specific handling for message formats:
026 * <ul>
027 * <li>LOCONETOPSBOARD</li>
028 * <li>LOCONETSV1MODE</li>
029 * <li>LOCONETSV2MODE</li>
030 * <li>LOCONETLNCVMODE</li>
031 * <li>LOCONETBDOPSWMODE</li>
032 * <li>LOCONETCSOPSWMODE</li>
033 * </ul>
034 * as defined in {@link LnProgrammerManager}
035 *
036 * Note that running a simulated LocoNet connection, {@link HexFileFrame#configure()} will substitute the
037 * {@link jmri.progdebugger.ProgDebugger} for the {@link jmri.jmrix.loconet.LnOpsModeProgrammer},
038 * overriding {@link #readCV(String, ProgListener)} and {@link #writeCV(String, int, ProgListener)}.
039 *
040 * @see jmri.Programmer
041 * @author Bob Jacobsen Copyright (C) 2002
042 * @author B. Milhaupt, Copyright (C) 2018
043 * @author Egbert Broerse, Copyright (C) 2020
044 */
045public class LnOpsModeProgrammer extends PropertyChangeSupport implements AddressedProgrammer, LocoNetListener {
046
047    LocoNetSystemConnectionMemo memo;
048    int mAddress;
049    boolean mLongAddr;
050    ProgListener p;
051    boolean doingWrite;
052    boolean boardOpSwWriteVal;
053    private int artNum;
054    private javax.swing.Timer bdOpSwAccessTimer = null;
055    private javax.swing.Timer sv2AccessTimer = null;
056    private javax.swing.Timer lncvAccessTimer = null;
057
058
059    public LnOpsModeProgrammer(LocoNetSystemConnectionMemo memo,
060            int pAddress, boolean pLongAddr) {
061        this.memo = memo;
062        mAddress = pAddress;
063        mLongAddr = pLongAddr;
064        // register to listen
065        memo.getLnTrafficController().addLocoNetListener(~0, this);
066    }
067
068    /**
069     * {@inheritDoc}
070     */
071    @Override
072    public void writeCV(String CV, int val, ProgListener pL) throws ProgrammerException {
073        p = null;
074        // Check mode
075        LocoNetMessage m;
076        if (getMode().equals(LnProgrammerManager.LOCONETCSOPSWMODE)) {
077            memo.getSlotManager().setMode(LnProgrammerManager.LOCONETCSOPSWMODE);
078            memo.getSlotManager().writeCV(CV, val, pL); // deal with this via service-mode programmer
079        } else if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) {
080            /*
081             * CV format is e.g. "113.12" where the first part defines the
082             * typeword for the specific board type and the second is the specific bit number
083             * Known values:
084             * <ul>
085             * <li>0x70 112 - PM4
086             * <li>0x71 113 - BDL16
087             * <li>0x72 114 - SE8
088             * <li>0x73 115 - DS64
089             * </ul>
090             */
091            if (bdOpSwAccessTimer == null) {
092                initializeBdOpsAccessTimer();
093            }
094            p = pL;
095            doingWrite = true;
096            // Board programming mode
097            log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress);
098            String[] parts = CV.split("\\.");
099            int typeWord = Integer.parseInt(parts[0]);
100            int state = Integer.parseInt(parts[parts.length>1 ? 1 : 0]);
101
102            // make message
103            m = new LocoNetMessage(6);
104            m.setOpCode(LnConstants.OPC_MULTI_SENSE);
105            int element = 0x72;
106            if ((mAddress & 0x80) != 0) {
107                element |= 1;
108            }
109            m.setElement(1, element);
110            m.setElement(2, (mAddress-1) & 0x7F);
111            m.setElement(3, typeWord);
112            int loc = (state - 1) / 8;
113            int bit = (state - 1) - loc * 8;
114            m.setElement(4, loc * 16 + bit * 2  + (val&0x01));
115
116            // save a copy of the written value for use during reply
117            boardOpSwWriteVal = ((val & 0x01) == 1);
118
119            log.debug("  Message {}", m);
120            memo.getLnTrafficController().sendLocoNetMessage(m);
121            bdOpSwAccessTimer.start();
122
123        } else if (getMode().equals(LnProgrammerManager.LOCONETSV1MODE)) {
124            p = pL;
125            doingWrite = true;
126            // SV1 mode
127            log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress);
128
129            // make message
130            int locoIOAddress = mAddress;
131            int locoIOSubAddress = ((mAddress+256)/256)&0x7F;
132            m = jmri.jmrix.loconet.locoio.LocoIO.writeCV(locoIOAddress, locoIOSubAddress, decodeCvNum(CV), val);
133            // force version 1 tag
134            m.setElement(4, 0x01);
135            log.debug("  Message {}", m);
136            memo.getLnTrafficController().sendLocoNetMessage(m);
137
138        } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) {
139            if (sv2AccessTimer == null) {
140                initializeSV2AccessTimer();
141            }
142            p = pL;
143            // SV2 mode
144            log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress);
145            // make message
146            m = new LocoNetMessage(16);
147            loadSV2MessageFormat(m, mAddress, decodeCvNum(CV), val);
148            m.setElement(3, 0x01); // 1 byte write
149            log.debug("  Message {}", m);
150            memo.getLnTrafficController().sendLocoNetMessage(m);
151            sv2AccessTimer.start();
152        } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) {
153            if (lncvAccessTimer == null) {
154                initializeLncvAccessTimer();
155            }
156            /*
157             * CV format is e.g. "5033.12" where the first part defines the
158             * article number (type/module class) for the board and the second is the specific bit number.
159             * Modules without their own art. no. use 65535 (broadcast mode).
160             */
161            // LNCV Module programming mode
162            String[] parts = CV.split("\\.");
163            if (parts.length > 1) {
164                artNum = Integer.parseInt(parts[0]); // stored for comparison
165            }
166            int cvNum = Integer.parseInt(parts[parts.length > 1 ? 1 : 0]);
167            p = pL;
168            doingWrite = true;
169            // LNCV mode
170            log.debug("write CV \"{}\" to {} addr:{} (art. {})", cvNum, val, mAddress, artNum);
171            // make message
172            m = createCvWriteRequest(artNum, cvNum, val);
173            // module must be in Programming mode (handled by LNCV tool), note that mAddress is not included in LNCV Write message
174            log.debug("  Message {}", m);
175            memo.getLnTrafficController().sendLocoNetMessage(m);
176            lncvAccessTimer.start();
177        } else if (getMode().equals(LnProgrammerManager.LOCONETOPSBOARD)) {
178            // LOCONETOPSBOARD decoder 
179            memo.getSlotManager().setAcceptAnyLACK();
180            memo.getSlotManager().writeCVOpsMode(CV, val, pL, mAddress, mLongAddr);
181        } else {
182            // DCC ops mode
183            memo.getSlotManager().writeCVOpsMode(CV, val, pL, mAddress, mLongAddr);
184        }
185    }
186
187    /**
188     * {@inheritDoc}
189     * @param CV the CV to read, could be a composite string that is split in this method te pass eg. the module type
190     * @param pL  the listener that will be notified of the read
191     */
192    @Override
193    public void readCV(String CV, ProgListener pL) throws ProgrammerException {
194        this.p = null;
195        // Check mode
196        String[] parts;
197        LocoNetMessage m;
198        if (getMode().equals(LnProgrammerManager.LOCONETCSOPSWMODE)) {
199            memo.getSlotManager().setMode(LnProgrammerManager.LOCONETCSOPSWMODE);
200            memo.getSlotManager().readCV(CV, pL); // deal with this via service-mode programmer
201        } else if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) {
202            /*
203             * CV format is e.g. "113.12" where the first part defines the
204             * typeword for the specific board type and the second is the specific bit number
205             * Known values:
206             * <ul>
207             * <li>0x70 112 - PM4
208             * <li>0x71 113 - BDL16
209             * <li>0x72 114 - SE8
210             * <li>0x73 115 - DS64
211             * </ul>
212             */
213            if (bdOpSwAccessTimer == null) {
214                initializeBdOpsAccessTimer();
215            }
216            p = pL;
217            doingWrite = false;
218            // Board programming mode
219            log.debug("read CV \"{}\" addr:{}", CV, mAddress);
220            parts = CV.split("\\.");
221            int typeWord = Integer.parseInt(parts[0]);
222            int state = Integer.parseInt(parts[parts.length>1 ? 1 : 0]);
223
224            // make message
225            m = new LocoNetMessage(6);
226            m.setOpCode(LnConstants.OPC_MULTI_SENSE);
227            int element = 0x62;
228            if ((mAddress & 0x80) != 0) {
229                element |= 1;
230            }
231            m.setElement(1, element);
232            m.setElement(2, (mAddress-1) & 0x7F);
233            m.setElement(3, typeWord);
234            int loc = (state - 1) / 8;
235            int bit = (state - 1) - loc * 8;
236            m.setElement(4, loc * 16 + bit * 2);
237
238            log.debug("  Message {}", m);
239            memo.getLnTrafficController().sendLocoNetMessage(m);
240            bdOpSwAccessTimer.start();
241
242        } else if (getMode().equals(LnProgrammerManager.LOCONETSV1MODE)) {
243            p = pL;
244            doingWrite = false;
245            // SV1 mode
246            log.debug("read CV \"{}\" addr:{}", CV, mAddress);
247            // make message
248            int locoIOAddress = mAddress&0xFF;
249            int locoIOSubAddress = ((mAddress+256)/256)&0x7F;
250            m = jmri.jmrix.loconet.locoio.LocoIO.readCV(locoIOAddress, locoIOSubAddress, decodeCvNum(CV));
251            // force version 1 tag
252            m.setElement(4, 0x01);
253            log.debug("  Message {}", m);
254            memo.getLnTrafficController().sendLocoNetMessage(m);
255
256        } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) {
257            if (sv2AccessTimer == null) {
258                initializeSV2AccessTimer();
259            }
260            p = pL;
261            // SV2 mode
262            log.debug("read CV \"{}\" addr:{}", CV, mAddress);
263            // make message
264            m = new LocoNetMessage(16);
265            loadSV2MessageFormat(m, mAddress, decodeCvNum(CV), 0);
266            m.setElement(3, 0x02); // 1 byte read
267            log.debug("  Message {}", m);
268            memo.getLnTrafficController().sendLocoNetMessage(m);
269            sv2AccessTimer.start();
270        } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) {
271            if (lncvAccessTimer == null) {
272                initializeLncvAccessTimer();
273            }
274            /*
275             * CV format passed by SymbolicProg is formed "5033.12", where the first part defines the
276             * article number (type/module class) for the board and the second is the specific bit number.
277             * Modules without their own art. no. use 65535 (broadcast mode), so cannot use decoder definition.
278             */
279            parts = CV.split("\\.");
280            if (parts.length > 1) {
281                artNum = Integer.parseInt(parts[0]); // stored for comparison
282            }
283            int cvNum = Integer.parseInt(parts[parts.length > 1 ? 1 : 0]);
284            doingWrite = false;
285            // numberformat "113.12" is simply consumed by ProgDebugger (HexFile sim connection)
286            p = pL;
287            // LNCV mode
288            log.debug("read LNCV \"{}\" addr:{}", CV, mAddress);
289            // make message
290            m = createCvReadRequest(artNum, mAddress, cvNum); // module must be in Programming mode (is handled by LNCV tool)
291            log.debug("  Message {}", m);
292            memo.getLnTrafficController().sendLocoNetMessage(m);
293            lncvAccessTimer.start();
294        } else if (getMode().equals(LnProgrammerManager.LOCONETOPSBOARD)) {
295            // LOCONETOPSBOARD decoder 
296            memo.getSlotManager().setAcceptAnyLACK();
297            memo.getSlotManager().readCVOpsMode(CV, pL, mAddress, mLongAddr);
298        } else {
299            // DCC ops mode
300            memo.getSlotManager().readCVOpsMode(CV, pL, mAddress, mLongAddr);
301        }
302    }
303
304    /**
305     * {@inheritDoc}
306     */
307    @Override
308    public void confirmCV(String CV, int val, ProgListener pL) throws ProgrammerException {
309        p = null;
310        // Check mode
311        if (getMode().equals(LnProgrammerManager.LOCONETCSOPSWMODE)) {
312            memo.getSlotManager().setMode(LnProgrammerManager.LOCONETCSOPSWMODE);
313            memo.getSlotManager().readCV(CV, pL); // deal with this via service-mode programmer
314        } else if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) {
315            readCV(CV, pL);
316        } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) {
317            // SV2 mode
318            log.warn("confirm CV \"{}\" addr:{} in SV2 mode not implemented", CV, mAddress);
319            notifyProgListenerEnd(pL, 0, ProgListener.UnknownError);
320        } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) {
321            // LNCV (Uhlenbrock) mode
322            log.warn("confirm CV \"{}\" addr:{} in LNCV mode not (yet) implemented", CV, mAddress);
323            readCV(CV, pL);
324            //notifyProgListenerEnd(pL, 0, ProgListener.UnknownError);
325        } else if (getMode().equals(LnProgrammerManager.LOCONETOPSBOARD)) {
326            // LOCONETOPSBOARD decoder 
327            memo.getSlotManager().setAcceptAnyLACK();
328            memo.getSlotManager().confirmCVOpsMode(CV, val, pL, mAddress, mLongAddr);
329        } else {
330            // DCC ops mode
331            memo.getSlotManager().confirmCVOpsMode(CV, val, pL, mAddress, mLongAddr);
332        }
333    }
334
335    /**
336     * {@inheritDoc}
337     */
338    @Override
339    public void message(LocoNetMessage m) {
340        log.debug("LocoNet message received: {}", m);
341        if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) {
342            // are we reading? If not, ignore
343            if (p == null) {
344                log.warn("received board-program reply message with no reply object: {}", m);
345                return;
346            }
347            // check for right type, unit
348            if (m.getOpCode() != LnConstants.OPC_LONG_ACK
349                    || ((m.getElement(1) != 0x00) && (m.getElement(1) != 0x50))) {
350                return;
351            }
352            // got a message that is LONG_ACK reply to an BdOpsSw access
353            bdOpSwAccessTimer.stop();    // kill the timeout timer
354            // LACK with 0x00 or 0x50 in byte 1; assume it's to us
355            if (doingWrite) {
356                int code = ProgListener.OK;
357                int val = (boardOpSwWriteVal ? 1 : 0);
358                ProgListener temp = p;
359                p = null;
360                notifyProgListenerEnd(temp, val, code);
361                return;
362            }
363
364            int val = 0;
365            if ((m.getElement(2) & 0x20) != 0) {
366                val = 1;
367            }
368
369            // successful read if LACK return status is not 0x7F
370            int code = ProgListener.OK;
371            if ((m.getElement(2) == 0x7f)) {
372                code = ProgListener.UnknownError;
373            }
374
375            ProgListener temp = p;
376            p = null;
377            notifyProgListenerEnd(temp, val, code);
378
379        } else if (getMode().equals(LnProgrammerManager.LOCONETSV1MODE)) {
380            // see if reply to LNSV 1 or LNSV2 request
381            if ((m.getOpCode() != LnConstants.OPC_PEER_XFER) ||
382                    (m.getElement( 1) != 0x10) ||
383                    (m.getElement( 4) != 0x01) || // format 1
384                    ((m.getElement( 5) & 0x70) != 0x00)) {
385                return;
386            }
387
388            // check for src address (?) moved to 0x50
389            // this might not be the right way to tell....
390            if ((m.getElement(3) & 0x7F) != 0x50) {
391                return;
392            }
393
394            // more checks needed? E.g. addresses?
395
396            // Mode 1 return data comes back in
397            // byte index 12, with the MSB in 0x01 of byte index 10
398            //
399
400            // check pending activity
401            if (p == null) {
402                log.warn("received SV reply message with no reply object: {}", m);
403            } else {
404                log.debug("returning SV programming reply: {}", m);
405                int code = ProgListener.OK;
406                int val;
407                if (doingWrite) {
408                    val = m.getPeerXfrData()[7];
409                } else {
410                    val = m.getPeerXfrData()[5];
411                }
412                ProgListener temp = p;
413                p = null;
414                notifyProgListenerEnd(temp, val, code);
415            }
416        } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) {
417            // see if reply to LNSV 1 or LNSV2 request
418            if (((m.getOpCode() & 0xFF) != LnConstants.OPC_PEER_XFER) ||
419                    ((m.getElement( 1) & 0xFF) != 0x10) ||
420                    ((m.getElement( 3) != 0x41) && (m.getElement(3) != 0x42)) || // need a "Write One Reply", or a "Read One Reply"
421                    ((m.getElement( 4) & 0xFF) != 0x02) || // format 2)
422                    ((m.getElement( 5) & 0x70) != 0x10) || // need SVX1 high nibble = 1
423                    ((m.getElement(10) & 0x70) != 0x10) // need SVX2 high nibble = 1
424                    ) {
425                return;
426            }
427            // more checks needed? E.g. addresses?
428
429            // return reply
430            if (p == null) {
431                log.error("received SV reply message with no reply object: {}", m);
432            } else {
433                log.debug("returning SV programming reply: {}", m);
434
435                sv2AccessTimer.stop();    // kill the timeout timer
436                
437                int code = ProgListener.OK;
438                int val = (m.getElement(11)&0x7F)|(((m.getElement(10)&0x01) != 0x00)? 0x80:0x00);
439
440                ProgListener temp = p;
441                p = null;
442                notifyProgListenerEnd(temp, val, code);
443            }
444        } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) {
445            // see if reply to LNCV request
446            // (compare this part to that in LNCV Tool jmri.jmrix.loconet.swing.lncvprog.LncvProgPane.message)
447            // is it a LACK write confirmation response from module?
448            int code;
449            if ((m.getOpCode() == LnConstants.OPC_LONG_ACK) &&
450                    (m.getElement(1) == 0x6D) && doingWrite) { // elem 1 = OPC (matches 0xED), elem 2 = ack1
451                // convert Uhlenbrock LNCV error codes to ProgListener codes, TODO extend that list to match?
452                switch (m.getElement(2)) {
453                    case 0x7f:
454                        code = ProgListener.OK;
455                        break;
456                    case 2:
457                    case 3:
458                        code = ProgListener.NotImplemented;
459                        break;
460                    case 1:
461                    default:
462                        code = ProgListener.UnknownError;
463                }
464                if (lncvAccessTimer != null) {
465                    lncvAccessTimer.stop(); // kill the timeout timer
466                }
467                // LACK with 0x00 or 0x50 in byte 1; assume it's to us.
468                ProgListener temp = p;
469                p = null;
470                notifyProgListenerEnd(temp, 0, code);
471            }
472            if ((LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY) ||
473            (LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY2)) {
474                // it's an LNCV ReadReply message, decode contents
475                LncvMessageContents contents = new LncvMessageContents(m);
476                int artReturned = contents.getLncvArticleNum();
477                int valReturned = contents.getCvValue();
478                code = ProgListener.OK;
479                // forward write reply
480                if (artReturned != artNum) { // it's not for us?
481                    //code = ProgListener.ConfirmFailed;
482                    log.warn("LNCV read reply received for article {}, expected article {}", artReturned, artNum);
483                }
484                if (lncvAccessTimer != null) {
485                    lncvAccessTimer.stop(); // kill the timeout timer
486                }
487                ProgListener temp = p;
488                p = null;
489                notifyProgListenerEnd(temp, valReturned, code);
490            }
491        }
492    }
493
494    int decodeCvNum(String CV) {
495        try {
496            return Integer.parseInt(CV);
497        } catch (java.lang.NumberFormatException e) {
498            return 0;
499        }
500    }
501
502    /** Fill in an SV2 format LocoNet message from parameters provided.
503     * Compare to SV2 message handler in {@link LnSv2MessageContents#createSv2Message(int, int, int, int, int, int, int, int)}
504     *
505     * @param m         Base LocoNet message to fill
506     * @param mAddress  Destination board address
507     * @param cvAddr    Dest. board CV number
508     * @param data      Value to put into CV
509     */
510    void loadSV2MessageFormat(LocoNetMessage m, int mAddress, int cvAddr, int data) {
511        m.setElement(0, LnConstants.OPC_PEER_XFER);
512        m.setElement(1, 0x10);
513        m.setElement(2, 0x01);
514        // 3 SV_CMD to be filled in later
515        m.setElement(4, 0x02);
516        // 5 will come back to SVX1
517        m.setElement(6, mAddress&0xFF);
518        m.setElement(7, (mAddress>>8)&0xFF);
519        m.setElement(8, cvAddr&0xFF);
520        m.setElement(9, (cvAddr/256)&0xFF);
521
522        // set SVX1
523        int svx1 = 0x10
524                    |((m.getElement(6)&0x80) != 0 ? 0x01 : 0)  // DST_L
525                    |((m.getElement(7)&0x80) != 0 ? 0x02 : 0)  // DST_L
526                    |((m.getElement(8)&0x80) != 0 ? 0x04 : 0)  // DST_L
527                    |((m.getElement(9)&0x80) != 0 ? 0x08 : 0); // SV_ADRH
528        m.setElement(5, svx1);
529        m.setElement(6, m.getElement(6)&0x7F);
530        m.setElement(7, m.getElement(7)&0x7F);
531        m.setElement(8, m.getElement(8)&0x7F);
532        m.setElement(9, m.getElement(9)&0x7F);
533
534        // 10 will come back to SVX2
535        m.setElement(11, data&0xFF);
536        m.setElement(12, (data>>8)&0xFF);
537        m.setElement(13, (data>>16)&0xFF);
538        m.setElement(14, (data>>24)&0xFF);
539
540        // set SVX2
541        int svx2 = 0x10
542                    |((m.getElement(11)&0x80) != 0 ? 0x01 : 0)
543                    |((m.getElement(12)&0x80) != 0 ? 0x02 : 0)
544                    |((m.getElement(13)&0x80) != 0 ? 0x04 : 0)
545                    |((m.getElement(14)&0x80) != 0 ? 0x08 : 0);
546        m.setElement(10, svx2);
547        m.setElement(11, m.getElement(11)&0x7F);
548        m.setElement(12, m.getElement(12)&0x7F);
549        m.setElement(13, m.getElement(13)&0x7F);
550        m.setElement(14, m.getElement(14)&0x7F);
551    }
552
553    // handle mode
554    protected ProgrammingMode mode = ProgrammingMode.OPSBYTEMODE;
555
556    /**
557     * {@inheritDoc}
558     */
559    @Override
560    public final void setMode(ProgrammingMode m) {
561        if (getSupportedModes().contains(m)) {
562            mode = m;
563            firePropertyChange("Mode", mode, m); // NOI18N
564        } else {
565            throw new IllegalArgumentException("Invalid requested mode: " + m); // NOI18N
566        }
567    }
568
569    /**
570     * {@inheritDoc}
571     */
572    @Override
573    public final ProgrammingMode getMode() {
574        return mode;
575    }
576
577    /**
578     * {@inheritDoc}
579     */
580    @Override
581    @Nonnull
582    public List<ProgrammingMode> getSupportedModes() {
583        List<ProgrammingMode> ret = new ArrayList<>(4);
584        ret.add(ProgrammingMode.OPSBYTEMODE);
585        ret.add(LnProgrammerManager.LOCONETOPSBOARD);
586        ret.add(LnProgrammerManager.LOCONETSV1MODE);
587        ret.add(LnProgrammerManager.LOCONETSV2MODE);
588        ret.add(LnProgrammerManager.LOCONETLNCVMODE);
589        ret.add(LnProgrammerManager.LOCONETBDOPSWMODE);
590        ret.add(LnProgrammerManager.LOCONETCSOPSWMODE);
591        return ret;
592    }
593
594    /**
595     * {@inheritDoc}
596     *
597     * Confirmation mode by programming mode; not that this doesn't
598     * yet know whether BDL168 hardware is present to allow DecoderReply
599     * to function; that should be a preference eventually. See also DCS240...
600     *
601     * @param addr CV address ignored, as there's no variance with this in LocoNet
602     * @return depends on programming mode
603     */
604    @Nonnull
605    @Override
606    public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) {
607        if (getMode().equals(ProgrammingMode.OPSBYTEMODE)) {
608            return WriteConfirmMode.NotVerified;
609        }
610        return WriteConfirmMode.DecoderReply;
611    }
612
613    /**
614     * {@inheritDoc}
615     *
616     * Can this ops-mode programmer read back values? Yes, if transponding
617     * hardware is present and regular ops mode, or if in any other mode.
618     *
619     * @return always true
620     */
621    @Override
622    public boolean getCanRead() {
623        if (getMode().equals(ProgrammingMode.OPSBYTEMODE)) return memo.getSlotManager().getTranspondingAvailable(); // only way can be false
624        return true;
625     }
626
627    /**
628     * {@inheritDoc}
629     */
630    @Override
631    public boolean getCanRead(String addr) {
632        return getCanRead();
633    }
634
635    /**
636     * {@inheritDoc}
637     */
638    @Override
639    public boolean getCanWrite() {
640        return true;
641    }
642
643    /**
644     * {@inheritDoc}
645     */
646    @Override
647    public boolean getCanWrite(String addr) {
648        return getCanWrite() && Integer.parseInt(addr) <= 1024;
649    }
650
651    /**
652     * {@inheritDoc}
653     */
654    @Override
655    @Nonnull
656    public String decodeErrorCode(int i) {
657        return memo.getSlotManager().decodeErrorCode(i);
658    }
659
660    /**
661     * {@inheritDoc}
662     */
663    @Override
664    public boolean getLongAddress() {
665        return mLongAddr;
666    }
667
668    /**
669     * {@inheritDoc}
670     */
671    @Override
672    public int getAddressNumber() {
673        return mAddress;
674    }
675
676    /**
677     * {@inheritDoc}
678     */
679    @Override
680    public String getAddress() {
681        return "" + getAddressNumber() + " " + getLongAddress();
682    }
683
684    void initializeBdOpsAccessTimer() {
685        if (bdOpSwAccessTimer == null) {
686            bdOpSwAccessTimer = new javax.swing.Timer(1000, (ActionEvent e) -> {
687                ProgListener temp = p;
688                p = null;
689                notifyProgListenerEnd(temp, 0, ProgListener.FailedTimeout);
690            });
691        bdOpSwAccessTimer.setInitialDelay(1000);
692        bdOpSwAccessTimer.setRepeats(false);
693        }
694    }
695
696    void initializeSV2AccessTimer() {
697        if (sv2AccessTimer == null) {
698            sv2AccessTimer = new javax.swing.Timer(1000, (ActionEvent e) -> {
699                ProgListener temp = p;
700                p = null;
701                notifyProgListenerEnd(temp, 0, ProgListener.FailedTimeout);
702            });
703        sv2AccessTimer.setInitialDelay(1000);
704        sv2AccessTimer.setRepeats(false);
705        }
706    }
707
708    void initializeLncvAccessTimer() {
709        if (lncvAccessTimer == null) {
710            lncvAccessTimer = new javax.swing.Timer(1000, (ActionEvent e) -> {
711                ProgListener temp = p;
712                p = null;
713                notifyProgListenerEnd(temp, 0, ProgListener.FailedTimeout);
714            });
715            lncvAccessTimer.setInitialDelay(1000);
716            lncvAccessTimer.setRepeats(false);
717        }
718    }
719
720    // initialize logging
721    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LnOpsModeProgrammer.class);
722
723}