001package jmri.jmrix.cmri.serial; 002 003import jmri.Turnout; 004import jmri.implementation.AbstractTurnout; 005import jmri.jmrix.cmri.CMRISystemConnectionMemo; 006import javax.annotation.Nonnull; 007import javax.annotation.CheckReturnValue; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Turnout implementation for C/MRI serial systems. 013 * <p> 014 * This object doesn't listen to the C/MRI communications. This is because it 015 * should be the only object that is sending messages for this turnout; more 016 * than one Turnout object pointing to a single device is not allowed. 017 * <p> 018 * Turnouts on the layout may be controlled by one or two output bits. To 019 * control a turnout from one Turnout object via two output bits, the output 020 * bits must be on the same node, the Turnout address must point to the first 021 * output bit, and the second output bit must follow the output bit at the next 022 * address. Valid states for the two bits controlling the two-bit turnout are: 023 * ON OFF, and OFF ON for the two bits. 024 * <p> 025 * This class can also drive pulsed outputs, which can be combined with the 026 * two-bit option in the expected ways. 027 * <p> 028 * When a Turnout is configured for pulsed and two-output, a request to go to a 029 * new CommandedState sets the desired configuration for the pulse interval, 030 * then sets both leads to their off condition. 031 * <p> 032 * When a Turnout is configured for pulsed and one output, a request to go to a 033 * new CommandedState just sets the output on for the interval; it's assumed 034 * that there's something out on the layout that converts that pulse into a 035 * "flip to other state" operation. 036 * <p> 037 * Finally, this implementation supports the "inverted" option. Inverted applies 038 * to the status of the lead on the C/MRI output itself. 039 * <p> 040 * For example, a pulsed, two-output, inverted turnout will have both pins set 041 * to 1 in the resting state. When THROWN, one lead will be set to 0 for the 042 * configured interval, then set back to 1. 043 * <p> 044 * For more discussion of this, please see the 045 * <a href="http://jmri.org/help/en/html/hardware/cmri/CMRI.shtml#options">documentation 046 * page</a>. 047 * 048 * @author Bob Jacobsen Copyright (C) 2003, 2007, 2008 049 * @author David Duchamp Copyright (C) 2004, 2007 050 * @author Dan Boudreau Copyright (C) 2007 051 */ 052public class SerialTurnout extends AbstractTurnout { 053 054 CMRISystemConnectionMemo _memo = null; 055 056 /** 057 * Create a Turnout object, with both system and user names. 058 * <p> 059 * 'systemName' was previously validated in SerialTurnoutManager 060 * @param systemName system name 061 * @param userName user name 062 * @param memo system connection 063 */ 064 public SerialTurnout(@Nonnull String systemName, String userName, CMRISystemConnectionMemo memo) { 065 super(systemName, userName); 066 // Save system Name 067 tSystemName = systemName; 068 _memo = memo; 069 // Extract the Bit from the name 070 tBit = _memo.getBitFromSystemName(systemName); 071 } 072 073 /** 074 * Handle a request to change state by sending a turnout command 075 * 076 * @param newState desired new state, one of the Turnout class constants 077 */ 078 @Override 079 protected void forwardCommandChangeToLayout(int newState) { 080 // implementing classes will typically have a function/listener to get 081 // updates from the layout, which will then call 082 // public void firePropertyChange(String propertyName, 083 // Object oldValue, 084 // Object newValue) 085 // _once_ if anything has changed state (or set the commanded state directly) 086 087 // sort out states 088 if ((newState & Turnout.CLOSED) != 0) { 089 // first look for the double case, which we can't handle 090 if ((newState & Turnout.THROWN) != 0) { 091 // this is the disaster case! 092 log.error("Cannot command both CLOSED and THROWN: {}", newState); 093 return; 094 } else { 095 // send a CLOSED command 096 sendMessage(true); 097 } 098 } else { 099 // send a THROWN command 100 sendMessage(false); 101 } 102 } 103 104 /** 105 * C/MRI turnouts do support inversion 106 */ 107 @Override 108 public boolean canInvert() { 109 return true; 110 } 111 112 @Override 113 protected void turnoutPushbuttonLockout(boolean _pushButtonLockout) { 114 log.debug("Send command to {} Pushbutton", (_pushButtonLockout ? "Lock" : "Unlock")); 115 } 116 117 // data members 118 String tSystemName; // System Name of this turnout 119 protected int tBit; // bit number of turnout control in Serial node 120 protected SerialNode tNode = null; 121 protected javax.swing.Timer mPulseClosedTimer = null; 122 protected javax.swing.Timer mPulseThrownTimer = null; 123 protected boolean mPulseTimerOn = false; 124 125 /** 126 * Control the actual layout hardware. The request is for a particular 127 * functional setting, e.g. CLOSED or THROWN. The "inverted" status of the 128 * output leads is handled here. 129 * @param closed True sets the turnout CLOSED 130 */ 131 protected void sendMessage(boolean closed) { 132 // if a Pulse Timer is running, ignore the call 133 if (!mPulseTimerOn) { 134 if (tNode == null) { 135 tNode = (SerialNode) _memo.getNodeFromSystemName(tSystemName, _memo.getTrafficController()); 136 if (tNode == null) { 137 // node does not exist, ignore call 138 log.error("Trying to set a C/MRI turnout that doesn't exist: {} - ignored", tSystemName); 139 return; 140 } 141 } 142 if (getNumberControlBits() == 1) { 143 // check for pulsed control 144 if (getControlType() == 0) { 145 // steady state control, get current status of the output bit 146 if ((tNode.getOutputBit(tBit) ^ getInverted()) != closed) { 147 // bit state is different from the requested state, set it 148 tNode.setOutputBit(tBit, closed ^ getInverted()); 149 } else { 150 // Bit state is the same as requested state, so nothing 151 // will happen if requested state is set. 152 // Check if turnout known state is different from requested state 153 int kState = getKnownState(); 154 if (closed) { 155 // CLOSED is being requested 156 if ((kState & Turnout.THROWN) != 0) { 157 // known state is different from output bit, set output bit to be correct 158 // for known state, then start a timer to set it to requested state 159 tNode.setOutputBit(tBit, false ^ getInverted()); 160 // start a timer to finish setting this turnout 161 if (mPulseClosedTimer == null) { 162 mPulseClosedTimer = new javax.swing.Timer(tNode.getPulseWidth(), new java.awt.event.ActionListener() { 163 @Override 164 public void actionPerformed(java.awt.event.ActionEvent e) { 165 tNode.setOutputBit(tBit, true ^ getInverted()); 166 mPulseClosedTimer.stop(); 167 mPulseTimerOn = false; 168 } 169 }); 170 } 171 mPulseTimerOn = true; 172 mPulseClosedTimer.start(); 173 } 174 } else { 175 // THROWN is being requested 176 if ((kState & Turnout.CLOSED) != 0) { 177 // known state is different from output bit, set output bit to be correct 178 // for known state, then start a timer to set it to requested state 179 tNode.setOutputBit(tBit, true ^ getInverted()); 180 // start a timer to finish setting this turnout 181 if (mPulseThrownTimer == null) { 182 mPulseThrownTimer = new javax.swing.Timer(tNode.getPulseWidth(), new java.awt.event.ActionListener() { 183 @Override 184 public void actionPerformed(java.awt.event.ActionEvent e) { 185 tNode.setOutputBit(tBit, false ^ getInverted()); 186 mPulseThrownTimer.stop(); 187 mPulseTimerOn = false; 188 } 189 }); 190 } 191 mPulseTimerOn = true; 192 mPulseThrownTimer.start(); 193 } 194 } 195 } 196 } else { 197 // Pulse control 198 int iTime = tNode.getPulseWidth(); 199 // Get current known state of turnout 200 int kState = getKnownState(); 201 if ((closed && ((kState & Turnout.THROWN) != 0)) 202 || (!closed && ((kState & Turnout.CLOSED) != 0))) { 203 // known and requested are different, a change is requested 204 // Pulse the line, first turn bit on 205 tNode.setOutputBit(tBit, false ^ getInverted()); 206 // Start a timer to return bit to off state 207 if (mPulseClosedTimer == null) { 208 mPulseClosedTimer = new javax.swing.Timer(iTime, new java.awt.event.ActionListener() { 209 @Override 210 public void actionPerformed(java.awt.event.ActionEvent e) { 211 tNode.setOutputBit(tBit, true ^ getInverted()); 212 mPulseClosedTimer.stop(); 213 mPulseTimerOn = false; 214 } 215 }); 216 } 217 mPulseTimerOn = true; 218 mPulseClosedTimer.start(); 219 } 220 } 221 } else if (getNumberControlBits() == 2) { 222 // two output bits 223 if (getControlType() == 0) { 224 // Steady state control e.g. stall motor turnout control 225 tNode.setOutputBit(tBit, closed ^ getInverted()); 226 tNode.setOutputBit(tBit + 1, !(closed ^ getInverted())); 227 } else { 228 // Pulse control, 2-bits 229 int iTime = tNode.getPulseWidth(); 230 // Get current known state of turnout 231 int kState = getKnownState(); 232 if (closed && ((kState & Turnout.THROWN) != 0)) { 233 // CLOSED is requested, currently THROWN - Pulse first bit 234 // Turn bit on 235 tNode.setOutputBit(tBit, false ^ getInverted()); 236 // Start a timer to return bit to off state 237 if (mPulseClosedTimer == null) { 238 mPulseClosedTimer = new javax.swing.Timer(iTime, new java.awt.event.ActionListener() { 239 @Override 240 public void actionPerformed(java.awt.event.ActionEvent e) { 241 tNode.setOutputBit(tBit, true ^ getInverted()); 242 mPulseClosedTimer.stop(); 243 mPulseTimerOn = false; 244 } 245 }); 246 } 247 mPulseTimerOn = true; 248 mPulseClosedTimer.start(); 249 } else if (!closed && ((kState & Turnout.CLOSED) != 0)) { 250 // THROWN is requested, currently CLOSED - Pulse second bit 251 // Turn bit on 252 tNode.setOutputBit(tBit + 1, false ^ getInverted()); 253 // Start a timer to return bit to off state 254 if (mPulseThrownTimer == null) { 255 mPulseThrownTimer = new javax.swing.Timer(iTime, new java.awt.event.ActionListener() { 256 @Override 257 public void actionPerformed(java.awt.event.ActionEvent e) { 258 tNode.setOutputBit(tBit + 1, true ^ getInverted()); 259 mPulseThrownTimer.stop(); 260 mPulseTimerOn = false; 261 } 262 }); 263 } 264 mPulseTimerOn = true; 265 mPulseThrownTimer.start(); 266 } 267 } 268 } 269 } 270 } 271 272 /** 273 * {@inheritDoc} 274 * 275 * Sorts by node number and then by bit 276 */ 277 @CheckReturnValue 278 @Override 279 public int compareSystemNameSuffix(@Nonnull String suffix1, @Nonnull String suffix2, @Nonnull jmri.NamedBean n) { 280 return CMRISystemConnectionMemo.compareSystemNameSuffix(suffix1, suffix2); 281 } 282 283 private final static Logger log = LoggerFactory.getLogger(SerialTurnout.class); 284 285}