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