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 * @return list of turnouts and their expected states if affected turnouts are correctly set; null otherwise. 062 */ 063 protected List<LayoutTrackExpectedState<LayoutTurnout>> checkTurnoutsInSection(Section s, int seqNum, Section nextSection, 064 ActiveTrain at, Section prevSection) { 065 return turnoutUtil(s, seqNum, nextSection, at, false, false, prevSection); 066 } 067 068 069 /** 070 * Set all turnouts for travel in the designated Section to the next 071 * Section. 072 * 073 * Checks that all turnouts are correctly set for travel in this Section to 074 * the next Section, and sets any turnouts that are not correct. The Section 075 * must be FREE to set its turnouts. Testing for FREE only occurs if a 076 * command needs to be issued. For a command to be issued to set a turnout, 077 * the Block containing that turnout must be unoccupied. NOTE: This method 078 * does not wait for turnout feedback--it assumes the turnout will be set 079 * correctly if a command is issued. 080 * 081 * NOTE: This method removes the need to specify the LayoutEditor panel. 082 * 083 * 084 * @param s the section to check 085 * @param seqNum sequence number for the section 086 * @param nextSection the following section 087 * @param at the associated train 088 * @param trustKnownTurnouts true to trust known turnouts 089 * @param prevSection the prior section 090 * 091 * @return list of turnouts and their expected states if affected turnouts are correctly set or commands have been 092 * issued to set any that aren't set correctly; null if a needed 093 * command could not be issued because the turnout's Block is 094 * occupied 095 */ 096 protected List<LayoutTrackExpectedState<LayoutTurnout>> setTurnoutsInSection(Section s, int seqNum, Section nextSection, 097 ActiveTrain at, boolean trustKnownTurnouts, Section prevSection) { 098 return turnoutUtil(s, seqNum, nextSection, at, trustKnownTurnouts, true, prevSection); 099 } 100 101 protected Turnout checkStateAgainstList(List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList) { 102 if (turnoutList != null) { 103 for (LayoutTrackExpectedState<LayoutTurnout> tes : turnoutList) { 104 Turnout to = tes.getObject().getTurnout(); 105 int setting = tes.getExpectedState(); 106 if (tes.getObject() instanceof LayoutSlip) { 107 setting = ((LayoutSlip) tes.getObject()).getTurnoutState(tes.getExpectedState()); 108 } 109 if (to.getKnownState() != setting) { 110 return to; 111 } 112 if (tes.getObject() instanceof LayoutSlip) { 113 //Look at the state of the second turnout in the slip 114 setting = ((LayoutSlip) tes.getObject()).getTurnoutBState(tes.getExpectedState()); 115 to = ((LayoutSlip) tes.getObject()).getTurnoutB(); 116 if (to.getKnownState() != setting) { 117 return to; 118 } 119 } 120 } 121 } 122 return null; 123 } 124 125 /** 126 * Internal method implementing the above two methods Returns 'true' if 127 * turnouts are set correctly, 'false' otherwise If 'set' is 'true' this 128 * routine will attempt to set the turnouts, if 'false' it reports what it 129 * finds. 130 */ 131 private List<LayoutTrackExpectedState<LayoutTurnout>> turnoutUtil(Section s, int seqNum, Section nextSection, 132 ActiveTrain at, boolean trustKnownTurnouts, boolean set, Section prevSection ) { 133 // initialize response structure 134 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutListForAllocatedSection = new ArrayList<>(); 135 // validate input and initialize 136 Transit tran = at.getTransit(); 137 if ((s == null) || (seqNum > tran.getMaxSequence()) || (!tran.containsSection(s))) { 138 log.error("Invalid argument when checking or setting turnouts in Section."); 139 return null; 140 } 141 int direction = at.getAllocationDirectionFromSectionAndSeq(s, seqNum); 142 if (direction == 0) { 143 log.error("Invalid Section/sequence arguments when checking or setting turnouts"); 144 return null; 145 } 146 // Did have this set to include SignalMasts as part of the && statement 147 //Sections created using Signal masts will generally only have a single entry/exit point. 148 // check for no turnouts in this section 149 if (_dispatcher.getSignalType() == DispatcherFrame.SIGNALHEAD && (s.getForwardEntryPointList().size() <= 1) && (s.getReverseEntryPointList().size() <= 1)) { 150 log.debug("No entry points lists"); 151 // no possibility of turnouts 152 return turnoutListForAllocatedSection; 153 } 154 // initialize connectivity utilities and beginning block pointers 155 EntryPoint entryPt = null; 156 if (prevSection != null) { 157 entryPt = s.getEntryPointFromSection(prevSection, direction); 158 } else if (!s.containsBlock(at.getStartBlock())) { 159 entryPt = s.getEntryPointFromBlock(at.getStartBlock(), direction); 160 } 161 EntryPoint exitPt = null; 162 if (nextSection != null) { 163 exitPt = s.getExitPointToSection(nextSection, direction); 164 } 165 Block curBlock; // must be in the section 166 Block prevBlock = null; // must start outside the section or be null 167 int curBlockSeqNum; // sequence number of curBlock in Section 168 if (entryPt != null) { 169 curBlock = entryPt.getBlock(); 170 prevBlock = entryPt.getFromBlock(); 171 curBlockSeqNum = s.getBlockSequenceNumber(curBlock); 172 } else if ( !at.isAllocationReversed() && s.containsBlock(at.getStartBlock())) { 173 curBlock = at.getStartBlock(); 174 curBlockSeqNum = s.getBlockSequenceNumber(curBlock); 175 //Get the previous block so that we can set the turnouts in the current block correctly. 176 if (direction == Section.FORWARD) { 177 prevBlock = s.getBlockBySequenceNumber(curBlockSeqNum - 1); 178 } else if (direction == Section.REVERSE) { 179 prevBlock = s.getBlockBySequenceNumber(curBlockSeqNum + 1); 180 } 181 } else if (at.isAllocationReversed() && s.containsBlock(at.getEndBlock())) { 182 curBlock = at.getEndBlock(); 183 curBlockSeqNum = s.getBlockSequenceNumber(curBlock); 184 //Get the previous block so that we can set the turnouts in the current block correctly. 185 if (direction == Section.REVERSE) { 186 prevBlock = s.getBlockBySequenceNumber(curBlockSeqNum + 1); 187 } else if (direction == Section.FORWARD) { 188 prevBlock = s.getBlockBySequenceNumber(curBlockSeqNum - 1); 189 } 190 } else { 191 192 //if (_dispatcher.getSignalType() == DispatcherFrame.SIGNALMAST) { 193 // //This can be considered normal where SignalMast Logic is used. 194 // return true; 195 //} 196 // this is an error but is it? It only happens when system is under stress 197 // which would point to a threading issue. 198 try { 199 log.error("[{}]direction[{}] Section[{}]Error in turnout check/set request - initial Block[{}] and Section[{}] mismatch", 200 at.getActiveTrainName(),at.isAllocationReversed(),s.getDisplayName(USERSYS), 201 at.getStartBlock().getUserName(),at.getEndBlock().getDisplayName(USERSYS)); 202 } catch (Exception ex ) { 203 log.warn("Exception while creating log error : {}", ex.getLocalizedMessage()); 204 } 205 return turnoutListForAllocatedSection; 206 } 207 208 Block nextBlock = null; 209 // may be either in the section or the first block in the next section 210 int nextBlockSeqNum = -1; // sequence number of nextBlock in Section (-1 indicates outside Section) 211 if (exitPt != null && curBlock == exitPt.getBlock()) { 212 // next Block is outside of the Section 213 nextBlock = exitPt.getFromBlock(); 214 } else { 215 // next Block is inside the Section 216 if (direction == Section.FORWARD) { 217 nextBlock = s.getBlockBySequenceNumber(curBlockSeqNum + 1); 218 nextBlockSeqNum = curBlockSeqNum + 1; 219 } else if (direction == Section.REVERSE) { 220 nextBlock = s.getBlockBySequenceNumber(curBlockSeqNum - 1); 221 nextBlockSeqNum = curBlockSeqNum - 1; 222 } 223 if ((nextBlock == null && 224 ((!at.isAllocationReversed() && curBlock != at.getEndBlock()) || 225 (at.isAllocationReversed() && curBlock != at.getStartBlock())))) { 226 log.error("[{}]Error in block sequence numbers when setting/checking turnouts.", 227 curBlock.getDisplayName(USERSYS)); 228 return null; 229 } 230 } 231 232 List<LayoutTrackExpectedState<LayoutTurnout>> turnoutList = new ArrayList<>(); 233 // get turnouts by Block 234 boolean turnoutsOK = true; 235 236 var layoutBlockManger = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class); 237 while (curBlock != null) { 238 /*No point in getting the list if the previous block is null as it will return empty and generate an error, 239 this will only happen on the first run. Plus working on the basis that the turnouts in the current block would have already of 240 been set correctly for the train to have arrived in the first place. 241 */ 242 243 if (prevBlock != null) { 244 var blockName = curBlock.getUserName(); 245 if (blockName != null) { 246 var lblock = layoutBlockManger.getLayoutBlock(blockName); 247 if (lblock != null) { 248 var panel = lblock.getMaxConnectedPanel(); 249 if (panel != null) { 250 var connection = new ConnectivityUtil(panel); 251 turnoutList = connection.getTurnoutList(curBlock, prevBlock, nextBlock, true); 252 } 253 } 254 } 255 } 256 // loop over turnouts checking and optionally setting turnouts 257 for (int i = 0; i < turnoutList.size(); i++) { 258 Turnout to = turnoutList.get(i).getObject().getTurnout(); 259 if (to == null ) { 260 // this should not happen due to prior selection 261 log.error("Found null Turnout reference at {}: {}", i, turnoutList.get(i).getObject()); 262 continue; // move to next loop, what else can we do? 263 } 264 // save for return 265 turnoutListForAllocatedSection.add(turnoutList.get(i)); 266 int setting = turnoutList.get(i).getExpectedState(); 267 if (turnoutList.get(i).getObject() instanceof LayoutSlip) { 268 setting = ((LayoutSlip) turnoutList.get(i).getObject()).getTurnoutState(turnoutList.get(i).getExpectedState()); 269 } 270 // check or ignore current setting based on flag, set in Options 271 if (!trustKnownTurnouts) { 272 log.debug("{}: setting turnout {} to {}", at.getTrainName(), to.getDisplayName(USERSYS), 273 (setting == Turnout.CLOSED ? closedText : thrownText)); 274 to.setCommandedState(setting); 275 try { 276 Thread.sleep(100); 277 } catch (InterruptedException ex) { 278 } //TODO: move this to separate thread 279 } else { 280 if (to.getKnownState() != setting) { 281 // turnout is not set correctly 282 if (set) { 283 // setting has been requested, is Section free and Block unoccupied 284 if ((s.getState() == Section.FREE) && (curBlock.getState() != Block.OCCUPIED)) { 285 // send setting command 286 log.debug("{}: turnout {} commanded to {}", at.getTrainName(), to.getDisplayName(USERSYS), 287 (setting == Turnout.CLOSED ? closedText : thrownText)); 288 to.setCommandedState(setting); 289 try { 290 Thread.sleep(100); 291 } catch (InterruptedException ex) { 292 } //TODO: move this to separate thread 293 } else { 294 turnoutsOK = false; 295 } 296 } else { 297 turnoutsOK = false; 298 } 299 } else { 300 log.debug("{}: turnout {} already {}, skipping", at.getTrainName(), to.getDisplayName(USERSYS), 301 (setting == Turnout.CLOSED ? closedText : thrownText)); 302 } 303 } 304 if (turnoutList.get(i).getObject() instanceof LayoutSlip) { 305 //Look at the state of the second turnout in the slip 306 setting = ((LayoutSlip) turnoutList.get(i).getObject()).getTurnoutBState(turnoutList.get(i).getExpectedState()); 307 to = ((LayoutSlip) turnoutList.get(i).getObject()).getTurnoutB(); 308 if (!trustKnownTurnouts) { 309 to.setCommandedState(setting); 310 } else if (to.getKnownState() != setting) { 311 // turnout is not set correctly 312 if (set) { 313 // setting has been requested, is Section free and Block unoccupied 314 if ((s.getState() == Section.FREE) && (curBlock.getState() != Block.OCCUPIED)) { 315 // send setting command 316 to.setCommandedState(setting); 317 } else { 318 turnoutsOK = false; 319 } 320 } else { 321 turnoutsOK = false; 322 } 323 } 324 } 325 } 326 if (turnoutsOK) { 327 // move to next Block if any 328 if (nextBlockSeqNum >= 0) { 329 prevBlock = curBlock; 330 curBlock = nextBlock; 331 if ((exitPt != null) && (curBlock == exitPt.getBlock())) { 332 // next block is outside of the Section 333 nextBlock = exitPt.getFromBlock(); 334 nextBlockSeqNum = -1; 335 } else { 336 if (direction == Section.FORWARD) { 337 nextBlockSeqNum++; 338 } else { 339 nextBlockSeqNum--; 340 } 341 nextBlock = s.getBlockBySequenceNumber(nextBlockSeqNum); 342 if (nextBlock == null) { 343 // there is no next Block 344 nextBlockSeqNum = -1; 345 } 346 } 347 } else { 348 curBlock = null; 349 } 350 } else { 351 curBlock = null; 352 } 353 } 354 if (turnoutsOK) { 355 return turnoutListForAllocatedSection; 356 } 357 return null; 358 } 359 360 private final static Logger log = LoggerFactory.getLogger(AutoTurnouts.class); 361}