001package jmri.jmrix.marklin; 002 003import jmri.Turnout; 004import jmri.implementation.AbstractTurnout; 005import org.slf4j.Logger; 006import org.slf4j.LoggerFactory; 007 008/** 009 * Implement a Turnout via Marklin communications. 010 * <p> 011 * This object doesn't listen to the Marklin communications. This is because it 012 * should be the only object that is sending messages for this turnout; more 013 * than one Turnout object pointing to a single device is not allowed. 014 * <p> 015 * Based on work by Bob Jacobsen 016 * 017 * @author Kevin Dickerson Copyright (C) 2012 018 * 019 */ 020public class MarklinTurnout extends AbstractTurnout 021 implements MarklinListener { 022 023 String prefix; 024 025 /** 026 * Marklin turnouts use the NMRA number (0-2040) as their numerical 027 * identification in the system name. 028 * 029 * @param number address of the turnout 030 * @param prefix system prefix 031 * @param etc connection traffic controller 032 */ 033 public MarklinTurnout(int number, String prefix, MarklinTrafficController etc) { 034 super(prefix + "T" + number); 035 _number = number; 036 this.prefix = prefix; 037 tc = etc; 038 tc.addMarklinListener(this); 039 } 040 041 MarklinTrafficController tc; 042 043 /** 044 * {@inheritDoc} 045 */ 046 @Override 047 protected void forwardCommandChangeToLayout(int newState) { 048 // implementing classes will typically have a function/listener to get 049 // updates from the layout, which will then call 050 // public void firePropertyChange(String propertyName, 051 // Object oldValue, 052 // Object newValue) 053 // _once_ if anything has changed state (or set the commanded state directly) 054 055 // sort out states 056 if ((newState & Turnout.CLOSED) != 0) { 057 // first look for the double case, which we can't handle 058 if ((newState & Turnout.THROWN) != 0) { 059 // this is the disaster case! 060 log.error("Cannot command both CLOSED and THROWN {}", newState); 061 return; 062 } else { 063 // send a CLOSED command 064 sendMessage(!getInverted()); 065 } 066 } else { 067 // send a THROWN command 068 sendMessage(getInverted()); 069 } 070 } 071 072 // data members 073 int _number; // turnout number 074 075 /** 076 * Set the turnout known state to reflect what's been observed from the 077 * command station messages. A change there means that somebody commanded a 078 * state change (by using a throttle), and that command has 079 * already taken effect. Hence we use "newCommandedState" to indicate it's 080 * taken place. Must be followed by "newKnownState" to complete the turnout 081 * action. 082 * 083 * @param state Observed state, updated state from command station 084 */ 085 synchronized void setCommandedStateFromCS(int state) { 086 if ((getFeedbackMode() != DIRECT)) { 087 return; 088 } 089 090 newCommandedState(state); 091 } 092 093 /** 094 * Set the turnout known state to reflect what's been observed from the 095 * command station messages. A change there means that somebody commanded a 096 * state change (by using a throttle), and that command has 097 * already taken effect. Hence we use "newKnownState" to indicate it's taken 098 * place. 099 * 100 * @param state Observed state, updated state from command station 101 */ 102 synchronized void setKnownStateFromCS(int state) { 103 newCommandedState(state); 104 if (getFeedbackMode() == DIRECT) { 105 newKnownState(state); 106 } 107 } 108 109 @Override 110 public void turnoutPushbuttonLockout(boolean b) { 111 } 112 113 /** 114 * Marklin turnouts can be inverted 115 */ 116 @Override 117 public boolean canInvert() { 118 return true; 119 } 120 121 final static int PROTOCOL_UNKNOWN = MarklinConstants.PROTOCOL_UNKNOWN; 122 final static int DCC = MarklinConstants.PROTOCOL_DCC; 123 final static int MM2 = MarklinConstants.PROTOCOL_MM2; 124 final static int SFX = MarklinConstants.PROTOCOL_SX; 125 126 int protocol = PROTOCOL_UNKNOWN; 127 128 /** 129 * Tell the layout to go to new state. 130 * 131 * @param newstate State of the turnout to be sent to the command station 132 */ 133 protected void sendMessage(final boolean newstate) { 134 MarklinMessage m = MarklinMessage.getSetTurnout(getCANAddress(), (newstate ? 1 : 0), 0x01); 135 tc.sendMarklinMessage(m, this); 136 137 jmri.util.TimerUtil.schedule(new java.util.TimerTask() { 138 boolean state = newstate; 139 140 @Override 141 public void run() { 142 try { 143 sendOffMessage((state ? 1 : 0)); 144 } catch (Exception e) { 145 log.error("Exception occurred while sending delayed off to turnout", e); 146 } 147 } 148 }, METERINTERVAL); 149 } 150 151 int getCANAddress() { 152 switch (protocol) { 153 case DCC: 154 return _number + MarklinConstants.DCCACCSTART - 1; 155 default: 156 return _number + MarklinConstants.MM1ACCSTART - 1; 157 } 158 } 159 160 // to listen for status changes from Marklin system 161 @Override 162 public void reply(MarklinReply m) { 163 if (m.getPriority() == MarklinConstants.PRIO_1 && m.getCommand() >= MarklinConstants.ACCCOMMANDSTART && m.getCommand() <= MarklinConstants.ACCCOMMANDEND) { 164 if (protocol == PROTOCOL_UNKNOWN) { 165 if (m.getAddress() == _number + MarklinConstants.MM1ACCSTART - 1) { 166 protocol = MM2; 167 } else if (m.getAddress() == _number + MarklinConstants.DCCACCSTART - 1) { 168 protocol = DCC; 169 } else { 170 //Message is not for us. 171 return; 172 } 173 } 174 if (m.getAddress() == getCANAddress()) { 175 switch (m.getElement(9)) { 176 case 0x00: 177 setKnownStateFromCS(Turnout.THROWN); 178 break; 179 case 0x01: 180 setKnownStateFromCS(Turnout.CLOSED); 181 break; 182 default: 183 log.warn("Unknown state command {}", m.getElement(9)); 184 } 185 } 186 } 187 } 188 189 @Override 190 public void message(MarklinMessage m) { 191 // messages are ignored 192 } 193 194 protected void sendOffMessage(int state) { 195 MarklinMessage m = MarklinMessage.getSetTurnout(getCANAddress(), state, 0x00); 196 tc.sendMarklinMessage(m, this); 197 } 198 199 static final int METERINTERVAL = 100; // msec wait before closed 200 201 private final static Logger log = LoggerFactory.getLogger(MarklinTurnout.class); 202 203}