001package jmri.jmrit.dispatcher; 002 003import java.util.ArrayList; 004import java.util.List; 005import jmri.Block; 006import jmri.EntryPoint; 007import jmri.InstanceManager; 008import jmri.Section; 009import jmri.Transit; 010import jmri.Turnout; 011import jmri.NamedBean.DisplayOptions; 012import jmri.jmrit.display.layoutEditor.ConnectivityUtil; 013import jmri.jmrit.display.layoutEditor.LayoutSlip; 014import jmri.jmrit.display.layoutEditor.LayoutTrackExpectedState; 015import jmri.jmrit.display.layoutEditor.LayoutTurnout; 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019/** 020 * Handles automatic checking and setting of turnouts when Dispatcher allocates 021 * a Section in a specific direction. 022 * <p> 023 * This file is part of JMRI. 024 * <p> 025 * JMRI is open source software; you can redistribute it and/or modify it under 026 * the terms of version 2 of the GNU General Public License as published by the 027 * Free Software Foundation. See the "COPYING" file for a copy of this license. 028 * <p> 029 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 030 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 031 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 032 * 033 * @author Dave Duchamp Copyright (C) 2008-2009 034 */ 035public class AutoTurnouts { 036 037 public AutoTurnouts(DispatcherFrame d) { 038 _dispatcher = d; 039 } 040 041 private static final DisplayOptions USERSYS = DisplayOptions.USERNAME_SYSTEMNAME; 042 private final String closedText = InstanceManager.turnoutManagerInstance().getClosedText(); 043 private final String thrownText = InstanceManager.turnoutManagerInstance().getThrownText(); 044 045 // operational variables 046 protected DispatcherFrame _dispatcher = null; 047 boolean userInformed = false; 048 049 /** 050 * Check that all turnouts are correctly set for travel in the designated 051 * Section to the next Section. NOTE: This method requires use of the 052 * connectivity stored in a Layout Editor panel. 053 * 054 * NOTE: This method removes the need to specify the LayoutEditor panel. 055 * 056 * @param s the section to check 057 * @param seqNum sequence number for the section 058 * @param nextSection the following section 059 * @param at the associated train 060 * @param prevSection the prior section 061 * @param useTurnoutConnectionDelay true if the turnout connection delay should be applied 062 * @return list of turnouts and their expected states if affected turnouts are correctly set; null otherwise. 063 */ 064 protected List<LayoutTrackExpectedState<LayoutTurnout>> checkTurnoutsInSection(Section s, int seqNum, Section nextSection, 065 ActiveTrain at, Section prevSection, boolean useTurnoutConnectionDelay) { 066 return turnoutUtil(s, seqNum, nextSection, at, false, false, prevSection, useTurnoutConnectionDelay); 067 } 068 069 070 /** 071 * Set all turnouts for travel in the designated Section to the next 072 * Section. 073 * 074 * Checks that all turnouts are correctly set for travel in this Section to 075 * the next Section, and sets any turnouts that are not correct. The Section 076 * must be FREE to set its turnouts. Testing for FREE only occurs if a 077 * command needs to be issued. For a command to be issued to set a turnout, 078 * the Block containing that turnout must be unoccupied. NOTE: This method 079 * does not wait for turnout feedback--it assumes the turnout will be set 080 * correctly if a command is issued. 081 * 082 * NOTE: This method removes the need to specify the LayoutEditor panel. 083 * 084 * 085 * @param s the section to check 086 * @param seqNum sequence number for the section 087 * @param nextSection the following section 088 * @param at the associated train 089 * @param trustKnownTurnouts true to trust known turnouts 090 * @param prevSection the prior section 091 * @param useTurnoutConnectionDelay true if the turnout connection delay should be applied 092 * 093 * @return list of turnouts and their expected states if affected turnouts are correctly set or commands have been 094 * issued to set any that aren't set correctly; null if a needed 095 * command could not be issued because the turnout's Block is 096 * occupied 097 */ 098 protected List<LayoutTrackExpectedState<LayoutTurnout>> setTurnoutsInSection(Section s, int seqNum, Section nextSection, 099 ActiveTrain at, boolean trustKnownTurnouts, Section prevSection, boolean useTurnoutConnectionDelay) { 100 return turnoutUtil(s, seqNum, nextSection, at, trustKnownTurnouts, true, prevSection, useTurnoutConnectionDelay); 101 } 102 103 protected Turnout checkStateAgainstList(List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList) { 104 if (turnoutList != null) { 105 for (LayoutTrackExpectedState<LayoutTurnout> tes : turnoutList) { 106 Turnout to = tes.getObject().getTurnout(); 107 int setting = tes.getExpectedState(); 108 if (tes.getObject() instanceof LayoutSlip) { 109 setting = ((LayoutSlip) tes.getObject()).getTurnoutState(tes.getExpectedState()); 110 } 111 if (to.getKnownState() != setting) { 112 return to; 113 } 114 if (tes.getObject() instanceof LayoutSlip) { 115 //Look at the state of the second turnout in the slip 116 setting = ((LayoutSlip) tes.getObject()).getTurnoutBState(tes.getExpectedState()); 117 to = ((LayoutSlip) tes.getObject()).getTurnoutB(); 118 if (to.getKnownState() != setting) { 119 return to; 120 } 121 } 122 } 123 } 124 return null; 125 } 126 127 /** 128 * Internal method implementing the above two methods Returns 'true' if 129 * turnouts are set correctly, 'false' otherwise If 'set' is 'true' this 130 * routine will attempt to set the turnouts, if 'false' it reports what it 131 * finds. 132 */ 133 private List<LayoutTrackExpectedState<LayoutTurnout>> turnoutUtil(Section s, int seqNum, Section nextSection, 134 ActiveTrain at, boolean trustKnownTurnouts, boolean set, Section prevSection, boolean useTurnoutConnectionDelay ) { 135 // initialize response structure 136 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutListForAllocatedSection = new ArrayList<>(); 137 // validate input and initialize 138 Transit tran = at.getTransit(); 139 if ((s == null) || (seqNum > tran.getMaxSequence()) || (!tran.containsSection(s))) { 140 log.error("Invalid argument when checking or setting turnouts in Section."); 141 return null; 142 } 143 int direction = at.getAllocationDirectionFromSectionAndSeq(s, seqNum); 144 if (direction == 0) { 145 log.error("Invalid Section/sequence arguments when checking or setting turnouts"); 146 return null; 147 } 148 // Did have this set to include SignalMasts as part of the && statement 149 //Sections created using Signal masts will generally only have a single entry/exit point. 150 // check for no turnouts in this section 151 if (_dispatcher.getSignalType() == DispatcherFrame.SIGNALHEAD && (s.getForwardEntryPointList().size() <= 1) && (s.getReverseEntryPointList().size() <= 1)) { 152 log.debug("No entry points lists"); 153 // no possibility of turnouts 154 return turnoutListForAllocatedSection; 155 } 156 // initialize connectivity utilities and beginning block pointers 157 EntryPoint entryPt = null; 158 if (prevSection != null) { 159 entryPt = s.getEntryPointFromSection(prevSection, direction); 160 } else if (!s.containsBlock(at.getStartBlock())) { 161 entryPt = s.getEntryPointFromBlock(at.getStartBlock(), direction); 162 } 163 EntryPoint exitPt = null; 164 if (nextSection != null) { 165 exitPt = s.getExitPointToSection(nextSection, direction); 166 } 167 Block curBlock; // must be in the section 168 Block prevBlock = null; // must start outside the section or be null 169 int curBlockSeqNum; // sequence number of curBlock in Section 170 if (entryPt != null) { 171 curBlock = entryPt.getBlock(); 172 prevBlock = entryPt.getFromBlock(); 173 curBlockSeqNum = s.getBlockSequenceNumber(curBlock); 174 } else if ( !at.isAllocationReversed() && s.containsBlock(at.getStartBlock())) { 175 curBlock = at.getStartBlock(); 176 curBlockSeqNum = s.getBlockSequenceNumber(curBlock); 177 //Get the previous block so that we can set the turnouts in the current block correctly. 178 if (direction == Section.FORWARD) { 179 prevBlock = s.getBlockBySequenceNumber(curBlockSeqNum - 1); 180 } else if (direction == Section.REVERSE) { 181 prevBlock = s.getBlockBySequenceNumber(curBlockSeqNum + 1); 182 } 183 } else if (at.isAllocationReversed() && s.containsBlock(at.getEndBlock())) { 184 curBlock = at.getEndBlock(); 185 curBlockSeqNum = s.getBlockSequenceNumber(curBlock); 186 //Get the previous block so that we can set the turnouts in the current block correctly. 187 if (direction == Section.REVERSE) { 188 prevBlock = s.getBlockBySequenceNumber(curBlockSeqNum + 1); 189 } else if (direction == Section.FORWARD) { 190 prevBlock = s.getBlockBySequenceNumber(curBlockSeqNum - 1); 191 } 192 } else { 193 194 //if (_dispatcher.getSignalType() == DispatcherFrame.SIGNALMAST) { 195 // //This can be considered normal where SignalMast Logic is used. 196 // return true; 197 //} 198 // this is an error but is it? It only happens when system is under stress 199 // which would point to a threading issue. 200 try { 201 log.error("[{}]direction[{}] Section[{}]Error in turnout check/set request - initial Block[{}] and Section[{}] mismatch", 202 at.getActiveTrainName(),at.isAllocationReversed(),s.getDisplayName(USERSYS), 203 at.getStartBlock().getUserName(),at.getEndBlock().getDisplayName(USERSYS)); 204 } catch (Exception ex ) { 205 log.warn("Exception while creating log error : {}", ex.getLocalizedMessage()); 206 } 207 return turnoutListForAllocatedSection; 208 } 209 210 Block nextBlock = null; 211 // may be either in the section or the first block in the next section 212 int nextBlockSeqNum = -1; // sequence number of nextBlock in Section (-1 indicates outside Section) 213 if (exitPt != null && curBlock == exitPt.getBlock()) { 214 // next Block is outside of the Section 215 nextBlock = exitPt.getFromBlock(); 216 } else { 217 // next Block is inside the Section 218 if (direction == Section.FORWARD) { 219 nextBlock = s.getBlockBySequenceNumber(curBlockSeqNum + 1); 220 nextBlockSeqNum = curBlockSeqNum + 1; 221 } else if (direction == Section.REVERSE) { 222 nextBlock = s.getBlockBySequenceNumber(curBlockSeqNum - 1); 223 nextBlockSeqNum = curBlockSeqNum - 1; 224 } 225 if ((nextBlock == null && 226 ((!at.isAllocationReversed() && curBlock != at.getEndBlock()) || 227 (at.isAllocationReversed() && curBlock != at.getStartBlock())))) { 228 log.error("[{}]Error in block sequence numbers when setting/checking turnouts.", 229 curBlock.getDisplayName(USERSYS)); 230 return null; 231 } 232 } 233 234 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList = new ArrayList<>(); 235 // get turnouts by Block 236 boolean turnoutsOK = true; 237 238 var layoutBlockManger = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 239 while (curBlock != null) { 240 /*No point in getting the list if the previous block is null as it will return empty and generate an error, 241 this will only happen on the first run. Plus working on the basis that the turnouts in the current block would have already of 242 been set correctly for the train to have arrived in the first place. 243 */ 244 245 if (prevBlock != null) { 246 var blockName = curBlock.getUserName(); 247 if (blockName != null) { 248 var lblock = layoutBlockManger.getLayoutBlock(blockName); 249 if (lblock != null) { 250 var panel = lblock.getMaxConnectedPanel(); 251 if (panel != null) { 252 var connection = new ConnectivityUtil(panel); 253 turnoutList = connection.getTurnoutList(curBlock, prevBlock, nextBlock, true); 254 } 255 } 256 } 257 } 258 // loop over turnouts checking and optionally setting turnouts 259 for (int i = 0; i < turnoutList.size(); i++) { 260 Turnout to = turnoutList.get(i).getObject().getTurnout(); 261 if (to == null ) { 262 // this should not happen due to prior selection 263 log.error("Found null Turnout reference at {}: {}", i, turnoutList.get(i).getObject()); 264 continue; // move to next loop, what else can we do? 265 } 266 // save for return 267 turnoutListForAllocatedSection.add(turnoutList.get(i)); 268 int setting = turnoutList.get(i).getExpectedState(); 269 if (turnoutList.get(i).getObject() instanceof LayoutSlip) { 270 setting = ((LayoutSlip) turnoutList.get(i).getObject()).getTurnoutState(turnoutList.get(i).getExpectedState()); 271 } 272 // check or ignore current setting based on flag, set in Options 273 if (!trustKnownTurnouts) { 274 log.debug("{}: setting turnout {} to {}", at.getTrainName(), to.getDisplayName(USERSYS), 275 (setting == Turnout.CLOSED ? closedText : thrownText)); 276 if (useTurnoutConnectionDelay) { 277 to.setCommandedStateAtInterval(setting); 278 } else { 279 to.setCommandedState(setting); 280 } 281 try { 282 Thread.sleep(100); 283 } catch (InterruptedException ex) { 284 } //TODO: move this to separate thread 285 } else { 286 if (to.getKnownState() != setting) { 287 // turnout is not set correctly 288 if (set) { 289 // setting has been requested, is Section free and Block unoccupied 290 if ((s.getState() == Section.FREE) && (curBlock.getState() != Block.OCCUPIED)) { 291 // send setting command 292 log.debug("{}: turnout {} commanded to {}", at.getTrainName(), to.getDisplayName(USERSYS), 293 (setting == Turnout.CLOSED ? closedText : thrownText)); 294 if (useTurnoutConnectionDelay) { 295 to.setCommandedStateAtInterval(setting); 296 } else { 297 to.setCommandedState(setting); 298 } 299 try { 300 Thread.sleep(100); 301 } catch (InterruptedException ex) { 302 } //TODO: move this to separate thread 303 } else { 304 turnoutsOK = false; 305 } 306 } else { 307 turnoutsOK = false; 308 } 309 } else { 310 log.debug("{}: turnout {} already {}, skipping", at.getTrainName(), to.getDisplayName(USERSYS), 311 (setting == Turnout.CLOSED ? closedText : thrownText)); 312 } 313 } 314 if (turnoutList.get(i).getObject() instanceof LayoutSlip) { 315 //Look at the state of the second turnout in the slip 316 setting = ((LayoutSlip) turnoutList.get(i).getObject()).getTurnoutBState(turnoutList.get(i).getExpectedState()); 317 to = ((LayoutSlip) turnoutList.get(i).getObject()).getTurnoutB(); 318 if (!trustKnownTurnouts) { 319 if (useTurnoutConnectionDelay) { 320 to.setCommandedStateAtInterval(setting); 321 } else { 322 to.setCommandedState(setting); 323 } 324 } else if (to.getKnownState() != setting) { 325 // turnout is not set correctly 326 if (set) { 327 // setting has been requested, is Section free and Block unoccupied 328 if ((s.getState() == Section.FREE) && (curBlock.getState() != Block.OCCUPIED)) { 329 // send setting command 330 if (useTurnoutConnectionDelay) { 331 to.setCommandedStateAtInterval(setting); 332 } else { 333 to.setCommandedState(setting); 334 } 335 } else { 336 turnoutsOK = false; 337 } 338 } else { 339 turnoutsOK = false; 340 } 341 } 342 } 343 } 344 if (turnoutsOK) { 345 // move to next Block if any 346 if (nextBlockSeqNum >= 0) { 347 prevBlock = curBlock; 348 curBlock = nextBlock; 349 if ((exitPt != null) && (curBlock == exitPt.getBlock())) { 350 // next block is outside of the Section 351 nextBlock = exitPt.getFromBlock(); 352 nextBlockSeqNum = -1; 353 } else { 354 if (direction == Section.FORWARD) { 355 nextBlockSeqNum++; 356 } else { 357 nextBlockSeqNum--; 358 } 359 nextBlock = s.getBlockBySequenceNumber(nextBlockSeqNum); 360 if (nextBlock == null) { 361 // there is no next Block 362 nextBlockSeqNum = -1; 363 } 364 } 365 } else { 366 curBlock = null; 367 } 368 } else { 369 curBlock = null; 370 } 371 } 372 if (turnoutsOK) { 373 return turnoutListForAllocatedSection; 374 } 375 return null; 376 } 377 378 private final static Logger log = LoggerFactory.getLogger(AutoTurnouts.class); 379}