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}