001package jmri;
002
003import javax.annotation.Nonnull;
004
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Generates an NMRA packet containing the correct payload to enable or disable
010 * pushbutton lockout. Currently supports the following Decoders NCE CVP AD4
011 *
012 *
013 *
014 * NCE is the easiest to implement, CV556 = 0 disable lockout, CV556 = 1 enable
015 * lockout
016 *
017 * CVP is a bit tricker, CV514 controls the lockout for four turnouts. Each
018 * turnout can have one or two button controls. Therefore the user must specify
019 * if they are using one or two buttons for each turnout.
020 *
021 * From the CVP user manual:
022 *
023 * Function CV514 Lock all inputs 0 Unlock 1 1 Unlock 2 4 Unlock 3 16 Unlock 4
024 * 64 Unlock all 85 Enable 2 button 255
025 *
026 * This routine assumes that for two button operations the following table is
027 * true:
028 *
029 * Lock all inputs 0 Unlock 1 3 Unlock 2 12 Unlock 3 48 Unlock 4 192 Unlock all
030 * 255
031 *
032 * Each CVP can operate up to four turnouts, lucky for us, they are sequential.
033 * Also note that CVP decoder's use the old legacy format for ops mode
034 * programming.
035 *
036 * @author Daniel Boudreau Copyright (C) 2007
037 *
038 */
039public class PushbuttonPacket {
040
041    /**
042     * Valid stationary decoder names
043     */
044    public final static String unknown = "None";
045    public final static String NCEname = "NCE_Rev_C";
046    public final static String CVP_1Bname = "CVP_AD4_1B";
047    public final static String CVP_2Bname = "CVP_AD4_2B";
048
049    private final static String[] VALIDDECODERNAMES = {unknown, NCEname, CVP_1Bname,
050        CVP_2Bname};
051
052    /**
053     * @param prefix system prefix.
054     * @param turnoutNum turnout number.
055     * @param locked true if locked, else false.
056     * @throws IllegalArgumentException if input not OK
057     * @return a DCC packet.
058     */
059    @Nonnull
060    public static byte[] pushbuttonPkt(@Nonnull String prefix, int turnoutNum, boolean locked) {
061
062        Turnout t = InstanceManager.turnoutManagerInstance().getBySystemName(prefix + turnoutNum);
063        byte[] bl;
064
065        if (t == null || t.getDecoderName() == null ) {
066            throw new IllegalArgumentException("No turnout or turnout decoder name");
067        } else if (unknown.equals(t.getDecoderName())) {
068            throw new IllegalArgumentException("Turnout decoder name is unknown");
069        } else if (NCEname.equals(t.getDecoderName())) {
070            if (locked) {
071                bl = NmraPacket.accDecoderPktOpsMode(turnoutNum, 556, 1);
072            } else {
073                bl = NmraPacket.accDecoderPktOpsMode(turnoutNum, 556, 0);
074            }
075            if (bl == null) {
076                throw new IllegalArgumentException("No valid DCC packet address");
077            }
078            return bl;
079
080        // Note CVP decoders use the old legacy accessory  format
081        } else if (CVP_1Bname.equals(t.getDecoderName())
082                || CVP_2Bname.equals(t.getDecoderName())) {
083            int CVdata = CVPturnoutLockout(prefix, turnoutNum);
084            bl = NmraPacket.accDecoderPktOpsModeLegacy(turnoutNum, 514, CVdata);
085            if (bl == null) {
086                throw new IllegalArgumentException("No valid DCC packet address");
087            }
088            return bl;
089        } else {
090            log.error("Invalid decoder name for turnout {}", turnoutNum);
091            throw new IllegalArgumentException("Illegal decoder name");
092        }
093    }
094
095    @Nonnull
096    public static String[] getValidDecoderNames() {
097        String[] arrayCopy = new String[VALIDDECODERNAMES.length];
098
099        System.arraycopy(VALIDDECODERNAMES, 0, arrayCopy, 0, VALIDDECODERNAMES.length);
100        return arrayCopy;
101    }
102
103    // builds the data byte for CVP decoders, builds based on JMRI's current
104    // knowledge of turnout pushbutton lockout states. If a turnout doesn't
105    // exist, assume single button operation.
106    private static int CVPturnoutLockout(@Nonnull String prefix, int turnoutNum) {
107
108        int CVdata = 0;
109        int oneButton = 1;       // one pushbutton enable
110        int twoButton = 3;       // two pushbutton enable
111        int modTurnoutNum = (turnoutNum - 1) & 0xFFC; // mask off bits, there are 4 turnouts per
112        // decoder
113
114        for (int i = 0; i < 4; i++) {
115            // set the default for one button in case the turnout doesn't exist
116            int button = oneButton;
117            modTurnoutNum++;
118            Turnout t = InstanceManager.turnoutManagerInstance()
119                    .getBySystemName(prefix + modTurnoutNum);
120            if (t != null && t.getDecoderName() != null) {
121                if (CVP_1Bname.equals(t.getDecoderName())) {
122                    // do nothing button already = oneButton
123                } else if (CVP_2Bname.equals(t.getDecoderName())) {
124                    button = twoButton;
125                } else {
126                    log.warn("Turnout {}, all CVP turnouts on one decoder should be " + CVP_1Bname + " or " + CVP_2Bname, modTurnoutNum);
127                }
128                // zero out the bits if the turnout is locked
129                if (t.getLocked(Turnout.PUSHBUTTONLOCKOUT)) {
130                    button = 0;
131                }
132            }
133            CVdata = CVdata + button;
134            oneButton = oneButton << 2; // move to the next turnout
135            twoButton = twoButton << 2;
136
137        }
138        return CVdata;
139    }
140
141    private final static Logger log = LoggerFactory.getLogger(PushbuttonPacket.class);
142}