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