001package jmri.jmrix.loconet;
002
003import java.awt.event.ActionEvent;
004import java.util.ArrayList;
005import java.util.List;
006import javax.annotation.Nonnull;
007import jmri.AddressedProgrammer;
008import jmri.ProgListener;
009import jmri.Programmer;
010import jmri.ProgrammerException;
011import jmri.ProgrammingMode;
012import jmri.beans.PropertyChangeSupport;
013import jmri.jmrix.loconet.hexfile.HexFileFrame;
014import jmri.jmrix.loconet.lnsvf2.LnSv2MessageContents;
015//import jmri.jmrix.loconet.swing.lncvprog.LncvProgPane;
016import jmri.jmrix.loconet.uhlenbrock.LncvMessageContents;
017
018import static jmri.jmrix.loconet.uhlenbrock.LncvMessageContents.createCvReadRequest;
019import static jmri.jmrix.loconet.uhlenbrock.LncvMessageContents.createCvWriteRequest;
020
021/**
022 * Provide an Ops Mode Programmer via a wrapper that works with the LocoNet
023 * SlotManager object.
024 * Specific handling for message formats:
025 * <ul>
026 * <li>LOCONETOPSBOARD</li>
027 * <li>LOCONETSV1MODE</li>
028 * <li>LOCONETSV2MODE</li>
029 * <li>LOCONETLNCVMODE</li>
030 * <li>LOCONETBDOPSWMODE</li>
031 * <li>LOCONETBD7OPSWMODE</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 low bit 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.LOCONETBD7OPSWMODE)) {
124            /*
125             * Normal CV format
126             */
127            if (bdOpSwAccessTimer == null) {
128                initializeBdOpsAccessTimer();
129            }
130            p = pL;
131            doingWrite = true;
132            // Board programming mode
133            log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress);
134            
135            // get prefix if any
136            String[] parts = CV.split("\\.");
137            int offset = 0;
138            int cv = 0;
139            switch (parts.length) {
140                case 1: // plain CV number
141                    cv = Integer.parseInt(parts[0])-1;
142                    break;
143                case 2:  //  offset.CV format
144                    offset = Integer.parseInt(parts[0]);
145                    cv = Integer.parseInt(parts[1])-1;
146                    break;
147                default:
148                    log.error("unexpected number of parts in CV {}", CV);
149            }
150
151            int address6th = ((mAddress-1+offset) >> 2) & 0x3F;
152            int upper3 = ~(((mAddress-1+offset) >> 8) & 0x07);
153            int lower2 = (mAddress-1+offset) & 0x03;
154            int address7th = ((upper3 << 4) & 0x70) | (lower2 << 1) | 0x08;
155            
156            // make message - send immediate packet with custom content
157            m = new LocoNetMessage(11);
158            m.setOpCode(0xED);
159            m.setElement(1, 0x0B);
160            m.setElement(2, 0x7F);
161            m.setElement(3, 0x54);
162            m.setElement(4, 0x07 | (((val >> 7) & 0x01)<<4));
163            m.setElement(5, address6th);
164            m.setElement(6, address7th);
165            m.setElement(7, 0x6C | ((cv >> 7) & 0x03));
166            m.setElement(8, cv&0x7F);  // CV number
167            m.setElement(9, val&0x7F); // Data
168
169            // save a copy of the written value low bit for use during reply
170            boardOpSwWriteVal = ((val & 0x01) == 1);
171
172            log.debug("  Message {}", m);
173            memo.getLnTrafficController().sendLocoNetMessage(m);
174            bdOpSwAccessTimer.start();
175
176        } else if (getMode().equals(LnProgrammerManager.LOCONETSV1MODE)) {
177            p = pL;
178            doingWrite = true;
179            // SV1 mode
180            log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress);
181
182            // make message
183            int locoIOAddress = mAddress;
184            int locoIOSubAddress = ((mAddress+256)/256)&0x7F;
185            m = jmri.jmrix.loconet.locoio.LocoIO.writeCV(locoIOAddress, locoIOSubAddress, decodeCvNum(CV), val);
186            // force version 1 tag
187            m.setElement(4, 0x01);
188            log.debug("  Message {}", m);
189            memo.getLnTrafficController().sendLocoNetMessage(m);
190
191        } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) {
192            if (sv2AccessTimer == null) {
193                initializeSV2AccessTimer();
194            }
195            p = pL;
196            // SV2 mode
197            log.debug("write CV \"{}\" to {} addr:{}", CV, val, mAddress);
198            // make message
199            m = new LocoNetMessage(16);
200            loadSV2MessageFormat(m, mAddress, decodeCvNum(CV), val);
201            m.setElement(3, 0x01); // 1 byte write
202            log.debug("  Message {}", m);
203            memo.getLnTrafficController().sendLocoNetMessage(m);
204            sv2AccessTimer.start();
205        } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) {
206            if (lncvAccessTimer == null) {
207                initializeLncvAccessTimer();
208            }
209            /*
210             * CV format is e.g. "5033.12" where the first part defines the
211             * article number (type/module class) for the board and the second is the specific bit number.
212             * Modules without their own art. no. use 65535 (broadcast mode).
213             */
214            // LNCV Module programming mode
215            String[] parts = CV.split("\\.");
216            if (parts.length > 1) {
217                artNum = Integer.parseInt(parts[0]); // stored for comparison
218            }
219            int cvNum = Integer.parseInt(parts[parts.length > 1 ? 1 : 0]);
220            p = pL;
221            doingWrite = true;
222            // LNCV mode
223            log.debug("write CV \"{}\" to {} addr:{} (art. {})", cvNum, val, mAddress, artNum);
224            // make message
225            m = createCvWriteRequest(artNum, cvNum, val);
226            // module must be in Programming mode (handled by LNCV tool), note that mAddress is not included in LNCV Write message
227            log.debug("  Message {}", m);
228            memo.getLnTrafficController().sendLocoNetMessage(m);
229            lncvAccessTimer.start();
230        } else {
231            // LOCONETOPSBOARD decoder i.e. getMode().equals(LnProgrammerManager.LOCONETOPSBOARD)
232            // and the remaining case of DCC ops mode
233            memo.getSlotManager().setAcceptAnyLACK();
234            memo.getSlotManager().writeCVOpsMode(CV, val, pL, mAddress, mLongAddr);
235        }
236    }
237
238    /**
239     * {@inheritDoc}
240     * @param CV the CV to read, could be a composite string that is split in this method te pass eg. the module type
241     * @param pL  the listener that will be notified of the read
242     */
243    @Override
244    public void readCV(String CV, ProgListener pL) throws ProgrammerException {
245        this.p = null;
246        // Check mode
247        String[] parts;
248        LocoNetMessage m;
249        if (getMode().equals(LnProgrammerManager.LOCONETCSOPSWMODE)) {
250            memo.getSlotManager().setMode(LnProgrammerManager.LOCONETCSOPSWMODE);
251            memo.getSlotManager().readCV(CV, pL); // deal with this via service-mode programmer
252        } else if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) {
253            /*
254             * CV format is e.g. "113.12" where the first part defines the
255             * typeword for the specific board type and the second is the specific bit number
256             * Known values:
257             * <ul>
258             * <li>0x70 112 - PM4
259             * <li>0x71 113 - BDL16
260             * <li>0x72 114 - SE8
261             * <li>0x73 115 - DS64
262             * </ul>
263             */
264            if (bdOpSwAccessTimer == null) {
265                initializeBdOpsAccessTimer();
266            }
267            p = pL;
268            doingWrite = false;
269            // Board programming mode
270            log.debug("read CV \"{}\" addr:{}", CV, mAddress);
271            parts = CV.split("\\.");
272            int typeWord = Integer.parseInt(parts[0]);
273            int state = Integer.parseInt(parts[parts.length>1 ? 1 : 0]);
274
275            // make message
276            m = new LocoNetMessage(6);
277            m.setOpCode(LnConstants.OPC_MULTI_SENSE);
278            int element = 0x62;
279            if ((mAddress & 0x80) != 0) {
280                element |= 1;
281            }
282            m.setElement(1, element);
283            m.setElement(2, (mAddress-1) & 0x7F);
284            m.setElement(3, typeWord);
285            int loc = (state - 1) / 8;
286            int bit = (state - 1) - loc * 8;
287            m.setElement(4, loc * 16 + bit * 2);
288
289            log.debug("  Message {}", m);
290            memo.getLnTrafficController().sendLocoNetMessage(m);
291            bdOpSwAccessTimer.start();
292
293        } else if (getMode().equals(LnProgrammerManager.LOCONETBD7OPSWMODE)) {
294            /*
295             * Normal CV format
296             */
297            if (bdOpSwAccessTimer == null) {
298                initializeBdOpsAccessTimer();
299            }
300            p = pL;
301            doingWrite = false;
302            // Board programming mode
303            log.debug("read CV \"{}\" addr:{}", CV, mAddress);
304
305            // get prefix if any
306            parts = CV.split("\\.");
307            int offset = 0;
308            int cv = 0;
309            switch (parts.length) {
310                case 1: // plain CV number
311                    cv = Integer.parseInt(parts[0])-1;
312                    break;
313                case 2:  //  offset.CV format
314                    offset = Integer.parseInt(parts[0]);
315                    cv = Integer.parseInt(parts[1])-1;
316                    break;
317                default:
318                    log.error("unexpected number of parts in CV {}", CV);
319            }
320
321            int address6th = ((mAddress-1+offset) >> 2) & 0x3F;
322            int upper3 = ~(((mAddress-1+offset) >> 8) & 0x07);
323            int lower2 = (mAddress-1+offset) & 0x03;
324            int address7th = ((upper3 << 4) & 0x70) | (lower2 << 1) | 0x08;
325            
326            // make message - send immediate packet with custom content
327            m = new LocoNetMessage(11);
328            m.setOpCode(0xED);
329            m.setElement(1, 0x0B);
330            m.setElement(2, 0x7F);
331            m.setElement(3, 0x54);
332            m.setElement(4, 0x07);
333            m.setElement(5, address6th);
334            m.setElement(6, address7th);
335            m.setElement(7, 0x64 | ((cv >> 7) & 0x03));
336            m.setElement(8, cv&0x7F);  // CV number
337            m.setElement(9, 0);
338
339            log.debug("  Message {}", m);
340            memo.getLnTrafficController().sendLocoNetMessage(m);
341            bdOpSwAccessTimer.start();
342
343        } else if (getMode().equals(LnProgrammerManager.LOCONETSV1MODE)) {
344            p = pL;
345            doingWrite = false;
346            // SV1 mode
347            log.debug("read CV \"{}\" addr:{}", CV, mAddress);
348            // make message
349            int locoIOAddress = mAddress&0xFF;
350            int locoIOSubAddress = ((mAddress+256)/256)&0x7F;
351            m = jmri.jmrix.loconet.locoio.LocoIO.readCV(locoIOAddress, locoIOSubAddress, decodeCvNum(CV));
352            // force version 1 tag
353            m.setElement(4, 0x01);
354            log.debug("  Message {}", m);
355            memo.getLnTrafficController().sendLocoNetMessage(m);
356
357        } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) {
358            if (sv2AccessTimer == null) {
359                initializeSV2AccessTimer();
360            }
361            p = pL;
362            // SV2 mode
363            log.debug("read CV \"{}\" addr:{}", CV, mAddress);
364            // make message
365            m = new LocoNetMessage(16);
366            loadSV2MessageFormat(m, mAddress, decodeCvNum(CV), 0);
367            m.setElement(3, 0x02); // 1 byte read
368            log.debug("  Message {}", m);
369            memo.getLnTrafficController().sendLocoNetMessage(m);
370            sv2AccessTimer.start();
371        } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) {
372            if (lncvAccessTimer == null) {
373                initializeLncvAccessTimer();
374            }
375            /*
376             * CV format passed by SymbolicProg is formed "5033.12", where the first part defines the
377             * article number (type/module class) for the board and the second is the specific bit number.
378             * Modules without their own art. no. use 65535 (broadcast mode), so cannot use decoder definition.
379             */
380            parts = CV.split("\\.");
381            if (parts.length > 1) {
382                artNum = Integer.parseInt(parts[0]); // stored for comparison
383            }
384            int cvNum = Integer.parseInt(parts[parts.length > 1 ? 1 : 0]);
385            doingWrite = false;
386            // numberformat "113.12" is simply consumed by ProgDebugger (HexFile sim connection)
387            p = pL;
388            // LNCV mode
389            log.debug("read LNCV \"{}\" addr:{}", CV, mAddress);
390            // make message
391            m = createCvReadRequest(artNum, mAddress, cvNum); // module must be in Programming mode (is handled by LNCV tool)
392            log.debug("  Message {}", m);
393            memo.getLnTrafficController().sendLocoNetMessage(m);
394            lncvAccessTimer.start();
395        } else if (getMode().equals(LnProgrammerManager.LOCONETOPSBOARD)) {
396            // LOCONETOPSBOARD decoder
397            log.trace("LOCONETOPSBOARD start operation");
398            memo.getSlotManager().setAcceptAnyLACK();
399            memo.getSlotManager().readCVOpsMode(CV, pL, mAddress, mLongAddr);
400        } else {
401            // DCC ops mode
402            memo.getSlotManager().readCVOpsMode(CV, pL, mAddress, mLongAddr);
403        }
404    }
405
406    /**
407     * {@inheritDoc}
408     */
409    @Override
410    public void confirmCV(String CV, int val, ProgListener pL) throws ProgrammerException {
411        p = null;
412        // Check mode
413        if (getMode().equals(LnProgrammerManager.LOCONETCSOPSWMODE)) {
414            memo.getSlotManager().setMode(LnProgrammerManager.LOCONETCSOPSWMODE);
415            memo.getSlotManager().readCV(CV, pL); // deal with this via service-mode programmer
416        } else if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) {
417            readCV(CV, pL);
418        } else if (getMode().equals(LnProgrammerManager.LOCONETBD7OPSWMODE)) {
419            readCV(CV, pL);
420        } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) {
421            // SV2 mode
422            log.warn("confirm CV \"{}\" addr:{} in SV2 mode not implemented", CV, mAddress);
423            notifyProgListenerEnd(pL, 0, ProgListener.UnknownError);
424        } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) {
425            // LNCV (Uhlenbrock) mode
426            log.warn("confirm CV \"{}\" addr:{} in LNCV mode not (yet) implemented", CV, mAddress);
427            readCV(CV, pL);
428            //notifyProgListenerEnd(pL, 0, ProgListener.UnknownError);
429        } else if (getMode().equals(LnProgrammerManager.LOCONETOPSBOARD)) {
430            // LOCONETOPSBOARD decoder
431            memo.getSlotManager().setAcceptAnyLACK();
432            memo.getSlotManager().confirmCVOpsMode(CV, val, pL, mAddress, mLongAddr);
433        } else {
434            // DCC ops mode
435            memo.getSlotManager().confirmCVOpsMode(CV, val, pL, mAddress, mLongAddr);
436        }
437    }
438
439    /**
440     * {@inheritDoc}
441     */
442    @Override
443    public void message(LocoNetMessage m) {
444        log.debug("LocoNet message received: {}", m);
445        if (getMode().equals(LnProgrammerManager.LOCONETBDOPSWMODE)) {
446            // are we programming? If not, ignore
447            if (p == null) {
448                log.warn("received board-program reply message with no reply object: {}", m);
449                return;
450            }
451            // check for right type, unit
452            if (m.getOpCode() != LnConstants.OPC_LONG_ACK
453                    || ((m.getElement(1) != 0x00) && (m.getElement(1) != 0x50))) {
454                return;
455            }
456            // got a message that is LONG_ACK reply to an BdOpsSw access
457            bdOpSwAccessTimer.stop();    // kill the timeout timer
458            // LACK with 0x00 or 0x50 in byte 1; assume it's to us
459            if (doingWrite) {
460                int code = ProgListener.OK;
461                int val = (boardOpSwWriteVal ? 1 : 0);
462                ProgListener temp = p;
463                p = null;
464                notifyProgListenerEnd(temp, val, code);
465                return;
466            }
467
468            int val = 0;
469            if ((m.getElement(2) & 0x20) != 0) {
470                val = 1;
471            }
472
473            // successful read if LACK return status is not 0x7F
474            int code = ProgListener.OK;
475            if ((m.getElement(2) == 0x7f)) {
476                code = ProgListener.UnknownError;
477            }
478
479            ProgListener temp = p;
480            p = null;
481            notifyProgListenerEnd(temp, val, code);
482
483        } else if (getMode().equals(LnProgrammerManager.LOCONETBD7OPSWMODE)) {
484            // are we programming? If not, ignore
485            if (p == null) {
486                log.warn("received board-program reply message with no reply object: {}", m);
487                return;
488            }
489            // check for right type, unit
490            if (m.getOpCode() != LnConstants.OPC_LONG_ACK) return;
491            if (! (m.getElement(1) == 0x6E
492                   || ( m.getElement(1) == 0x6D)
493                        && (m.getElement(1) != 0x55 && m.getElement(1) != 0x5A) )) {
494                return;
495            }
496            // got a message that is LONG_ACK reply to an BdOpsSw access
497            bdOpSwAccessTimer.stop();    // kill the timeout timer
498            // LACK with 0x6E in byte 1; assume it's to us
499            if (doingWrite
500                    && m.getElement(1) == 0x6D
501                    && (m.getElement(2) == 0x55 || m.getElement(2) == 0x5A)) {
502                int code = ProgListener.OK;
503                int val = (boardOpSwWriteVal ? 1 : 0);
504                ProgListener temp = p;
505                p = null;
506                notifyProgListenerEnd(temp, val, code);
507                return;
508            }
509
510            if (m.getElement(1) != 0x6E) return;
511            // does this properly handle high bit of return value?
512            // Check the reply sequence for a 2nd 6D LACK?
513            int val = m.getElement(2);
514
515            // successful read always
516            int code = ProgListener.OK;
517
518            ProgListener temp = p;
519            p = null;
520            notifyProgListenerEnd(temp, val, code);
521
522        } else if (getMode().equals(LnProgrammerManager.LOCONETSV1MODE)) {
523            // see if reply to LNSV 1 or LNSV2 request
524            if ((m.getOpCode() != LnConstants.OPC_PEER_XFER) ||
525                    (m.getElement( 1) != 0x10) ||
526                    (m.getElement( 4) != 0x01) || // format 1
527                    ((m.getElement( 5) & 0x70) != 0x00)) {
528                return;
529            }
530
531            // check for src address (?) moved to 0x50
532            // this might not be the right way to tell....
533            if ((m.getElement(3) & 0x7F) != 0x50) {
534                return;
535            }
536
537            // more checks needed? E.g. addresses?
538
539            // Mode 1 return data comes back in
540            // byte index 12, with the MSB in 0x01 of byte index 10
541            //
542
543            // check pending activity
544            if (p == null) {
545                log.warn("received SV reply message with no reply object: {}", m);
546            } else {
547                log.debug("returning SV programming reply: {}", m);
548                int code = ProgListener.OK;
549                int val;
550                if (doingWrite) {
551                    val = m.getPeerXfrData()[7];
552                } else {
553                    val = m.getPeerXfrData()[5];
554                }
555                ProgListener temp = p;
556                p = null;
557                notifyProgListenerEnd(temp, val, code);
558            }
559        } else if (getMode().equals(LnProgrammerManager.LOCONETSV2MODE)) {
560            // see if reply to LNSV 1 or LNSV2 request
561            if (((m.getOpCode() & 0xFF) != LnConstants.OPC_PEER_XFER) ||
562                    ((m.getElement( 1) & 0xFF) != 0x10) ||
563                    ((m.getElement( 3) != 0x41) && (m.getElement(3) != 0x42)) || // need a "Write One Reply", or a "Read One Reply"
564                    ((m.getElement( 4) & 0xFF) != 0x02) || // format 2)
565                    ((m.getElement( 5) & 0x70) != 0x10) || // need SVX1 high nibble = 1
566                    ((m.getElement(10) & 0x70) != 0x10) // need SVX2 high nibble = 1
567                    ) {
568                return;
569            }
570            // more checks needed? E.g. addresses?
571
572            // return reply
573            if (p == null) {
574                log.error("received SV reply message with no reply object: {}", m);
575            } else {
576                log.debug("returning SV programming reply: {}", m);
577
578                sv2AccessTimer.stop();    // kill the timeout timer
579
580                int code = ProgListener.OK;
581                int val = (m.getElement(11)&0x7F)|(((m.getElement(10)&0x01) != 0x00)? 0x80:0x00);
582
583                ProgListener temp = p;
584                p = null;
585                notifyProgListenerEnd(temp, val, code);
586            }
587        } else if (getMode().equals(LnProgrammerManager.LOCONETLNCVMODE)) {
588            // see if reply to LNCV request
589            // (compare this part to that in LNCV Tool jmri.jmrix.loconet.swing.lncvprog.LncvProgPane.message)
590            // is it a LACK write confirmation response from module?
591            int code;
592            if ((m.getOpCode() == LnConstants.OPC_LONG_ACK) &&
593                    (m.getElement(1) == 0x6D) && doingWrite) { // elem 1 = OPC (matches 0xED), elem 2 = ack1
594                // convert Uhlenbrock LNCV error codes to ProgListener codes, TODO extend that list to match?
595                switch (m.getElement(2)) {
596                    case 0x7f:
597                        code = ProgListener.OK;
598                        break;
599                    case 2:
600                    case 3:
601                        code = ProgListener.NotImplemented;
602                        break;
603                    case 1:
604                    default:
605                        code = ProgListener.UnknownError;
606                }
607                if (lncvAccessTimer != null) {
608                    lncvAccessTimer.stop(); // kill the timeout timer
609                }
610                // LACK with 0x00 or 0x50 in byte 1; assume it's to us.
611                ProgListener temp = p;
612                p = null;
613                notifyProgListenerEnd(temp, 0, code);
614            }
615            if ((LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY) ||
616            (LncvMessageContents.extractMessageType(m) == LncvMessageContents.LncvCommand.LNCV_READ_REPLY2)) {
617                // it's an LNCV ReadReply message, decode contents
618                LncvMessageContents contents = new LncvMessageContents(m);
619                int artReturned = contents.getLncvArticleNum();
620                int valReturned = contents.getCvValue();
621                code = ProgListener.OK;
622                // forward write reply
623                if (artReturned != artNum) { // it's not for us?
624                    //code = ProgListener.ConfirmFailed;
625                    log.warn("LNCV read reply received for article {}, expected article {}", artReturned, artNum);
626                }
627                if (lncvAccessTimer != null) {
628                    lncvAccessTimer.stop(); // kill the timeout timer
629                }
630                ProgListener temp = p;
631                p = null;
632                notifyProgListenerEnd(temp, valReturned, code);
633            }
634        }
635    }
636
637    int decodeCvNum(String CV) {
638        try {
639            return Integer.parseInt(CV);
640        } catch (java.lang.NumberFormatException e) {
641            return 0;
642        }
643    }
644
645    /** Fill in an SV2 format LocoNet message from parameters provided.
646     * Compare to SV2 message handler in {@link LnSv2MessageContents#createSv2Message(int, int, int, int, int, int, int, int)}
647     *
648     * @param m         Base LocoNet message to fill
649     * @param mAddress  Destination board address
650     * @param cvAddr    Dest. board CV number
651     * @param data      Value to put into CV
652     */
653    void loadSV2MessageFormat(LocoNetMessage m, int mAddress, int cvAddr, int data) {
654        m.setElement(0, LnConstants.OPC_PEER_XFER);
655        m.setElement(1, 0x10);
656        m.setElement(2, 0x01);
657        // 3 SV_CMD to be filled in later
658        m.setElement(4, 0x02);
659        // 5 will come back to SVX1
660        m.setElement(6, mAddress&0xFF);
661        m.setElement(7, (mAddress>>8)&0xFF);
662        m.setElement(8, cvAddr&0xFF);
663        m.setElement(9, (cvAddr/256)&0xFF);
664
665        // set SVX1
666        int svx1 = 0x10
667                    |((m.getElement(6)&0x80) != 0 ? 0x01 : 0)  // DST_L
668                    |((m.getElement(7)&0x80) != 0 ? 0x02 : 0)  // DST_L
669                    |((m.getElement(8)&0x80) != 0 ? 0x04 : 0)  // DST_L
670                    |((m.getElement(9)&0x80) != 0 ? 0x08 : 0); // SV_ADRH
671        m.setElement(5, svx1);
672        m.setElement(6, m.getElement(6)&0x7F);
673        m.setElement(7, m.getElement(7)&0x7F);
674        m.setElement(8, m.getElement(8)&0x7F);
675        m.setElement(9, m.getElement(9)&0x7F);
676
677        // 10 will come back to SVX2
678        m.setElement(11, data&0xFF);
679        m.setElement(12, (data>>8)&0xFF);
680        m.setElement(13, (data>>16)&0xFF);
681        m.setElement(14, (data>>24)&0xFF);
682
683        // set SVX2
684        int svx2 = 0x10
685                    |((m.getElement(11)&0x80) != 0 ? 0x01 : 0)
686                    |((m.getElement(12)&0x80) != 0 ? 0x02 : 0)
687                    |((m.getElement(13)&0x80) != 0 ? 0x04 : 0)
688                    |((m.getElement(14)&0x80) != 0 ? 0x08 : 0);
689        m.setElement(10, svx2);
690        m.setElement(11, m.getElement(11)&0x7F);
691        m.setElement(12, m.getElement(12)&0x7F);
692        m.setElement(13, m.getElement(13)&0x7F);
693        m.setElement(14, m.getElement(14)&0x7F);
694    }
695
696    // handle mode
697    protected ProgrammingMode mode = ProgrammingMode.OPSBYTEMODE;
698
699    /**
700     * {@inheritDoc}
701     */
702    @Override
703    public final void setMode(ProgrammingMode m) {
704        if (getSupportedModes().contains(m)) {
705            mode = m;
706            firePropertyChange("Mode", mode, m); // NOI18N
707        } else {
708            throw new IllegalArgumentException("Invalid requested mode: " + m); // NOI18N
709        }
710    }
711
712    /**
713     * {@inheritDoc}
714     */
715    @Override
716    public final ProgrammingMode getMode() {
717        return mode;
718    }
719
720    /**
721     * {@inheritDoc}
722     */
723    @Override
724    @Nonnull
725    public List<ProgrammingMode> getSupportedModes() {
726        List<ProgrammingMode> ret = new ArrayList<>(4);
727        ret.add(ProgrammingMode.OPSBYTEMODE);
728        ret.add(LnProgrammerManager.LOCONETBD7OPSWMODE);
729        ret.add(LnProgrammerManager.LOCONETOPSBOARD);
730        ret.add(LnProgrammerManager.LOCONETSV1MODE);
731        ret.add(LnProgrammerManager.LOCONETSV2MODE);
732        ret.add(LnProgrammerManager.LOCONETLNCVMODE);
733        ret.add(LnProgrammerManager.LOCONETBDOPSWMODE);
734        ret.add(LnProgrammerManager.LOCONETCSOPSWMODE);
735        return ret;
736    }
737
738    /**
739     * {@inheritDoc}
740     *
741     * Confirmation mode by programming mode; not that this doesn't
742     * yet know whether BDL168 hardware is present to allow DecoderReply
743     * to function; that should be a preference eventually. See also DCS240...
744     *
745     * @param addr CV address ignored, as there's no variance with this in LocoNet
746     * @return depends on programming mode
747     */
748    @Nonnull
749    @Override
750    public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) {
751        if (getMode().equals(ProgrammingMode.OPSBYTEMODE)) {
752            return WriteConfirmMode.NotVerified;
753        }
754        return WriteConfirmMode.DecoderReply;
755    }
756
757    /**
758     * {@inheritDoc}
759     *
760     * Can this ops-mode programmer read back values? Yes, if transponding
761     * hardware is present and regular ops mode, or if in any other mode.
762     *
763     * @return always true
764     */
765    @Override
766    public boolean getCanRead() {
767        if (getMode().equals(ProgrammingMode.OPSBYTEMODE)) return memo.getSlotManager().getTranspondingAvailable(); // only way can be false
768        return true;
769     }
770
771    /**
772     * {@inheritDoc}
773     */
774    @Override
775    public boolean getCanRead(String addr) {
776        return getCanRead();
777    }
778
779    /**
780     * {@inheritDoc}
781     */
782    @Override
783    public boolean getCanWrite() {
784        return true;
785    }
786
787    /**
788     * {@inheritDoc}
789     */
790    @Override
791    public boolean getCanWrite(String addr) {
792        return getCanWrite() && Integer.parseInt(addr) <= 1024;
793    }
794
795    /**
796     * {@inheritDoc}
797     */
798    @Override
799    @Nonnull
800    public String decodeErrorCode(int i) {
801        return memo.getSlotManager().decodeErrorCode(i);
802    }
803
804    /**
805     * {@inheritDoc}
806     */
807    @Override
808    public boolean getLongAddress() {
809        return mLongAddr;
810    }
811
812    /**
813     * {@inheritDoc}
814     */
815    @Override
816    public int getAddressNumber() {
817        return mAddress;
818    }
819
820    /**
821     * {@inheritDoc}
822     */
823    @Override
824    public String getAddress() {
825        return "" + getAddressNumber() + " " + getLongAddress();
826    }
827
828    void initializeBdOpsAccessTimer() {
829        if (bdOpSwAccessTimer == null) {
830            bdOpSwAccessTimer = new javax.swing.Timer(1000, (ActionEvent e) -> {
831                ProgListener temp = p;
832                p = null;
833                notifyProgListenerEnd(temp, 0, ProgListener.FailedTimeout);
834            });
835        bdOpSwAccessTimer.setInitialDelay(1000);
836        bdOpSwAccessTimer.setRepeats(false);
837        }
838    }
839
840    void initializeSV2AccessTimer() {
841        if (sv2AccessTimer == null) {
842            sv2AccessTimer = new javax.swing.Timer(1000, (ActionEvent e) -> {
843                ProgListener temp = p;
844                p = null;
845                notifyProgListenerEnd(temp, 0, ProgListener.FailedTimeout);
846            });
847        sv2AccessTimer.setInitialDelay(1000);
848        sv2AccessTimer.setRepeats(false);
849        }
850    }
851
852    void initializeLncvAccessTimer() {
853        if (lncvAccessTimer == null) {
854            lncvAccessTimer = new javax.swing.Timer(1000, (ActionEvent e) -> {
855                ProgListener temp = p;
856                p = null;
857                notifyProgListenerEnd(temp, 0, ProgListener.FailedTimeout);
858            });
859            lncvAccessTimer.setInitialDelay(1000);
860            lncvAccessTimer.setRepeats(false);
861        }
862    }
863
864    // initialize logging
865    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LnOpsModeProgrammer.class);
866
867}