# AutoDispatcher 2 # # This script provides full layout automation, using connectivity info # provided by Layout Editor panels. # # This file is part of JMRI. # # JMRI is free software; you can redistribute it and/or modify it under # the terms of version 2 of the GNU General Public License as published # by the Free Software Foundation. See the "COPYING" file for a copy # of this license. # # JMRI is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # Author: Giorgio Terdina copyright (c) 2009 # # 2.01 beta - Fixed problem wih signalheads without UserName # 2.02 beta - Added test for empty sections # 2.03 beta - Added test for no valid section found # 2.04 beta - Corrected removal of SignalMasts # 2.05 beta - Reverted signals to red aspect as soon as train enters next block # 2.06 beta - Added new methods (getScale, getDeceleration, etc.) for custom AE # 2.07 beta - Corrected bug when minimum speed is selected in SignalType # 2.08 beta - Removed default speed from Speeds window, since it was confusing # 2.09 beta - Corrected problem when manually changing train's section # 2.10 beta - Corrected initialization of allocationReady at train departure # 2.11 beta - Removed bell chime for warnings printed before loading preferences # 2.12 beta - Modified Signal Edit window to make it more understandable # 2.13 beta - Compensated difference for emergency stop between Multimaus and other CS # 2.14 beta - Fixed bug for unbalanced brackets in schedule # 2.15 beta - Added detailed error messages for wrong schedule # 2.16 beta - Fixed problem of $P stopping trains controlled by "Braker" engineer # 2.17 beta - Added possibility of separating schedule tokens with comas "," # 2.18 beta - Added error message when destination is a transit-only section # 2.19 beta - Set default engineer to Auto, when custom script is not found # 2.20 beta - Added blinking of new flashingLunar signal aspect (since JMRI 2.7.8) # 2.21 beta - Corrected problem with OFF:Fx block action # 2.22 beta - Added test for wrong input in Locomotives window # 2.23 beta - Added possibility of stopping trains at the beginning of sections # 2.24 beta - Fixed hang-up when schedule alternative has only one section and its name is wrong # 2.25 beta - Added error message for nested "[" in schedule # 2.26 beta - Fixed problem with static variable in ADTrain addressed as self.variable # 2.27 beta - Fixed direction in transit-only sections when dealing with reversing tracks # 2.28 beta - Closed "Train Detail" window when "Apply" is clicked # 2.29 beta - Corrected loop in match method caused when an unknown section name was found # 2.30 beta - Modified "import" statements to reflect package structure of JMRI 2.9.x # 2.31 beta - Unified versions for JMRI 2.8 and 2.9.x and initialized block tracking at startup # 2.32 beta - Implementd "train start actions" and "AutoStart trains" option. # 2.33 beta - Enabled pause/resume buttons while script being stopped. # 2.34 beta - Updated blinkSignals as previous methods were deprecated (Greg). # 2.35 - Added $IFH (if held) command in schedule. # 2.35 - Added $TC and $TT (set turnout or accessory) commands in schedule. # 2.35 - Added $ST (Start at fast clock time) command in schedule. # 2.35 - Released throttle when train is in manual section. # 2.35 - Added custom section RGB colors. # JAVA imports from java.beans import PropertyChangeListener from java.lang import System from java.awt import BorderLayout from java.awt import Color from java.awt import GridLayout from java.awt import Toolkit from java.awt.event import ActionListener from java.awt.event import WindowAdapter from java.io import FileInputStream from java.io import FileOutputStream from java.io import IOException from java.io import ObjectInputStream from java.io import ObjectOutputStream from java.util import Locale from java.util import Random from javax.swing import BorderFactory from javax.swing import BoxLayout from javax.swing import ButtonGroup from javax.swing import JButton from javax.swing import JCheckBox from javax.swing import JComboBox from javax.swing import JFileChooser from javax.swing import JLabel from javax.swing import JOptionPane from javax.swing import JPanel from javax.swing import JRadioButton from javax.swing import JScrollPane from javax.swing import JTextArea from javax.swing import JTextField from javax.swing import JToggleButton from javax.swing import JSpinner from javax.swing import SpinnerNumberModel from javax.swing.event import ChangeListener from javax.swing.filechooser import FileFilter from math import sqrt from time import sleep from thread import start_new_thread # JMRI imports from apps import Apps from jmri import Block from jmri import DccThrottle from jmri import InstanceManager from jmri import PowerManager from jmri import Section from jmri import SectionManager from jmri import Sensor from jmri import SignalHead from jmri import Turnout from jmri.jmrit import Sound from jmri.jmrit import XmlFile from jmri.jmrit.consisttool import ConsistToolFrame from jmri.implementation import AbstractShutDownTask from jmri.jmrit.operations.locations import LocationManager from jmri.jmrit.operations.rollingstock.cars import CarManager from jmri.jmrit.operations.trains import TrainManager from jmri.jmrit.roster import Roster from jmri.util import JmriJFrame # Utility class for static methods ============== # Must be defined before being used! class ADstaticMethod : def __init__(self, anycallable): self.__call__ = anycallable # Fast Clock Listener class FastListener(java.beans.PropertyChangeListener): fastTime = 0 def propertyChange(self, event): time = InstanceManager.timebaseInstance().getTime() FastListener.fastTime = time.getHours() * 60 + time.getMinutes() return # MAIN CLASS ============== class AutoDispatcher(jmri.jmrit.automat.AbstractAutomaton) : # CONSTANTS ========================== version = "2.35" # Retrieve DOUBLE_XOVER constant, depending on JMRI Version # (LayoutTurnout class was moved to a new package, starting with JMRI 2.9.3) try : DOUBLE_XOVER = jmri.jmrit.display.layoutEditor.LayoutTurnout.DOUBLE_XOVER except : DOUBLE_XOVER = jmri.jmrit.display.LayoutTurnout.DOUBLE_XOVER # Maximum number of lines kept in the status scroll area MAX_LINES = 200 # Names of Signalhead aspects headsAspects = { "Dark": SignalHead.DARK, "Red": SignalHead.RED, "Yellow": SignalHead.YELLOW, "Green": SignalHead.GREEN, "Lunar": SignalHead.LUNAR, "Flash-Red": SignalHead.FLASHRED, "Flash-Yellow": SignalHead.FLASHYELLOW, "Flash-Green": SignalHead.FLASHGREEN, "Flash-Lunar": SignalHead.FLASHLUNAR } # Signalhead aspects inverse dictionary inverseAspects = {} for key in headsAspects.keys() : inverseAspects[headsAspects[key]] = key # STATIC VARIABLES ========================== # Our unique instance instance = None # Power monitor instance powerMonitor = None # Fast Clock fastBase = InstanceManager.timebaseInstance() fastListener = FastListener() # Status variables error = False loop = False stopped = True exiting = False paused = False repaint = False simulation = False trainsDirty = False preferencesDirty = False debug = False # Our random numbers generator random = Random() # Window frames mainFrame = None directionFrame = None panelFrame = None speedsFrame = None indicationsFrame = None signalTypesFrame = None signalEditFrame = None signalMastsFrame = None sectionsFrame = None blocksFrame = None locationsFrame = None preferencesFrame = None soundListFrame = None soundDefaultFrame = None locosFrame = None trainsFrame = None trainDetailFrame = None importFrame = None # Status area at the bottom of the main window statusScroll = JTextArea("Getting layout description (be patient!)", 4, 22) statusScroll.setEditable(False) statusScroll.setLineWrap(True) statusScroll.setWrapStyleWord(True) # Info gathered from JAVA screenSize = Toolkit.getDefaultToolkit().getScreenSize() # Info gathered from JMRI # List of signalHeads defined in JMRI signalHeadNames = [] # List of signalHeadIcons displayed on the panel signalIcons = [] # Original click mode of signalHeadIcons (used to restore them on exit) signalClick = [] # maximum number of blocks encountered in any section maxBlocksPerSection = 0 # Number of trains running runningTrains = 0 # List of available engineer scripts engineers = {} # Lists of last DCC commands sent # Used when resuming operations after "Power off" turnoutCommands = {} signalCommands = {} # Table used for test purposes if file AutoDispatcher_Loc.bin is not found locomotives = [ ['1017', 1017, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0], ['1019', 1019, [0.15, 0.5, 0.55, 0.6, 0.65], 12, 7, 0, 90.0], ['1633', 1633, [0.2, 0.65, 0.7, 0.75, 0.8], 10, 6, 0, 0.0], ['1641', 1641, [0.2, 0.5, 0.7, 0.8, 0.8], 15, 10, 0, 0.0], ['3023', 3023, [0.2, 0.6, 0.7, 0.8, 0.85], 8, 7, 0, 0.0], ['3029', 3029, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0], ['4802', 4802, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0], ['4805', 4805, [0.2, 0.7, 0.8, 0.9, 0.95], 10, 8, 0, 0.0], ['5010', 5010, [0.2, 0.6, 0.75, 0.85, 0.95], 10, 8, 0, 0.0], ['5151', 5151, [0.3, 0.6, 0.7, 0.8, 0.8], 12, 12, 0, 0.0], ['5160', 5160, [0.2, 0.6, 0.7, 0.8, 0.8], 15, 6, 0, 0.0], ['5162', 5162, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0], ['5166', 5166, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0], ['5294', 5294, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0], ['5307', 5307, [0.2, 0.8, 0.8, 0.85, 0.9], 5, 10, 0, 0.0]] # Variable used by "printTime" debug utility lastTime = 0 # STATIC METHODS ========================== def getVersion() : return AutoDispatcher.version getVersion = ADstaticMethod(getVersion) def setDebug(on) : AutoDispatcher.debug = on setDebug = ADstaticMethod(setDebug) def message(text) : # Output a message only in verbose mode if ADsettings.verbose : AutoDispatcher.log(text) message = ADstaticMethod(message) def log(text) : # Output a message, both to script main window and to log file AutoDispatcher.statusScroll.append("\n" + text) while (AutoDispatcher.statusScroll.getLineCount() > AutoDispatcher.MAX_LINES) : AutoDispatcher.statusScroll.replaceRange(None, 0, AutoDispatcher.statusScroll.getLineEndOffset(0)) AutoDispatcher.statusScroll.setCaretPosition( AutoDispatcher.statusScroll.getDocument().getLength()) print text log = ADstaticMethod(log) def chimeLog(sound, text) : # As log, but also plays a sound AutoDispatcher.log(text) ind = ADsettings.defaultSounds[sound] if ADsettings.ringBell and ind > 0 and ind >= len(ADsettings.soundList) : ADsettings.soundList[ind-1].play() chimeLog = ADstaticMethod(chimeLog) def addEngineer(engineerName, engineerClass) : # Add an Engineer script to our list if (engineerName != "Auto" or not AutoDispatcher.engineers.has_key(engineerName)) : AutoDispatcher.engineers[engineerName] = engineerClass if AutoDispatcher.trainsFrame != None : AutoDispatcher.trainsFrame.reDisplay() if engineerName != "Auto" : AutoDispatcher.log("Added engineer: " + engineerName) return True AutoDispatcher.log("Warning: Engineer " + engineerName + "already registered!") return False addEngineer = ADstaticMethod(addEngineer) def removeEngineer(engineerName) : # Remove an Engineer script from our list if (engineerName != "Auto" and AutoDispatcher.engineers.has_key(engineerName)) : del AutoDispatcher.engineers[engineerName] for train in ADtrain.getList() : if train.engineerName == engineerName : train.setEngineer("Auto") AutoDispatcher.log("Removed engineer: " + engineerName) return True AutoDispatcher.log("Warning: Engineer " + engineerName + " not found. Cannot be removed!") return False removeEngineer = ADstaticMethod(removeEngineer) def setTrainsDirty() : # Take note that trains info changed if AutoDispatcher.trainsDirty : return AutoDispatcher.trainsDirty = True if not AutoDispatcher.loop : ADmainMenu.saveTrainsButton.enabled = True setTrainsDirty = ADstaticMethod(setTrainsDirty) def setPreferencesDirty() : # Take note that preferences were changed if AutoDispatcher.preferencesDirty : return AutoDispatcher.preferencesDirty = True if not AutoDispatcher.loop : ADmainMenu.saveSettingsButton.enabled = True setPreferencesDirty = ADstaticMethod(setPreferencesDirty) def centerLabel(string) : # Create a centered JLabel tempLabel = JLabel(string) tempLabel.setHorizontalAlignment(JLabel.CENTER) return tempLabel centerLabel = ADstaticMethod(centerLabel) def printTime(text) : # Debug utility. Can be used to measure time lapsed between events newTime = System.currentTimeMillis() if AutoDispatcher.lastTime != 0 : print (newTime - AutoDispatcher.lastTime), text AutoDispatcher.lastTime = newTime printTime = ADstaticMethod(printTime) def cleanName(name) : # Replace spaces, brackest and $ contained in section or signal names # to avoid errors in schedules name = name.replace(" ", "_") name = name.replace("$", "#") name = name.replace("(", "{") name = name.replace("[", "{") name = name.replace(")", "}") name = name.replace("]", "}") name = name.replace(",", ".") return name cleanName = ADstaticMethod(cleanName) # INSTANCE METHODS ========================== # INITIAL SETUP ============== def setup(self): # Done once # Save the (unique) instance in a static variable, to allow access # from different classes AutoDispatcher.instance = self # Create settings ADsettings() # Register our own engineer AutoDispatcher.addEngineer("Auto", ADengineer) # Span a separate thread to setup script without blocking JMRI start_new_thread(self.__setup__, ()) def __setup__(self): # The real setup. #Check if we are running in simulation mode # (when running as statup script we must wait for connections # to be established) for i in range(60) : try : if (Apps.getConnection1().upper().find("SIMULATOR") < 0 and Apps.getConnection2().upper().find("SIMULATOR") < 0 and Apps.getConnection3().upper().find("SIMULATOR") < 0 and Apps.getConnection4().upper().find("SIMULATOR") < 0) : AutoDispatcher.simulation = False else : AutoDispatcher.simulation = True retrieveError = False break except : retrieveError = True self.waitMsec(1000) if retrieveError : print ("Sorry, unable to run AutoDispatcher as startup script!" + "Launch it manually.") AutoDispatcher.error = True return # Now perform initialization # Display the main window AutoDispatcher.mainFrame = ADmainMenu() # Get layout connectivity self.getLayoutData() if AutoDispatcher.error : return # Workout default settings self.defaultSettings() # Keep copy of automatic settings, to compare them with # user defined settings at saving time self.autoSections = ADsection.getSectionsTable() self.autoBlocks = ADsection.getBlocksTable() # If running in simulation mode, clear all sections before # placing trains on tracks if AutoDispatcher.simulation : AutoDispatcher.log("Clearing sensors for simulation") for block in ADblock.getList() : sensor = block.getOccupancySensor() if sensor != None : sensor.setKnownState(Sensor.INACTIVE) # Wait to allow JMRI processing of sensor change events self.waitMsec(1000) # Set power ON, otherwise user will wonder why trains # don't start :-) AutoDispatcher.powerMonitor.setPower(PowerManager.ON) # Set demo preferences (will be used if preferences files are not found) AutoDispatcher.log("Restoring settings") # Choose preferences on the basis of Layout Editor panel title title = self.layoutEditor.getTitle() if ADexamples1.examples.has_key(title) : ADsettings.load(ADexamples1.examples[title]) self.trains = ADexamples1.exampleTrains[title] elif ADexamples2.examples.has_key(title) : ADsettings.load(ADexamples2.examples[title]) self.trains = ADexamples2.exampleTrains[title] else : self.trains = [] # Get user settings from disk (if any) # User settings files are named on the basis of the first word # in Layout Editor panel title. # Get first word of panel title firstWord = title.split() if len(firstWord) == 0 : firstWord = "" else : firstWord = firstWord[0] baseName = "" # Strip out all characters but letters and numbers for c in firstWord : if c.isalnum() : baseName += c # If we didn't get a valid name, use "AutoDispatcher" if baseName == "" : baseName = "AutoDispatcher" self.settingsFile = (Roster.instance().getFileLocation() + baseName + "_AdP.bin") self.trainFile = (Roster.instance().getFileLocation() + baseName + "_AdT.bin") # Locomotive settings are not based on panel title, since there is # only one JMRI roster self.locoFile = (Roster.instance().getFileLocation() + "AutoDispatcher_Loc.bin") # Now try loading files self.loadSettings() self.loadLocomotives() self.loadTrains() # Apply user (or default) settings self.userSettings() # Get data from Operations module ADlocation.getOpLocations() # Setup completed # Enable buttons, unless some error occurred if not AutoDispatcher.error : AutoDispatcher.mainFrame.enableButtons(True) AutoDispatcher.chimeLog(ADsettings.START_STOP_SOUND, "Layout ready!") # LAYOUT CONNECTIVITY ============== def getLayoutData(self) : # Retrieve power monitor (will be used to check whether # the layout is powered) AutoDispatcher.powerMonitor = ADpowerMonitor() # Retrieve LayoutEditor instance by searching the relevant window # If more than one panel are open, we will get the first one we find windowsList = JmriJFrame.getFrameList() self.layoutEditor = None for i in range(windowsList.size()) : window = windowsList.get(i) windowClass = str(window.getClass()) if(windowClass.endswith(".LayoutEditor")) : self.layoutEditor = window break if self.layoutEditor == None : AutoDispatcher.log("No Layout Editor window found," + " script cannot continue!") AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Load your Layout Editor panel" + " before running AutoDispatcher!") AutoDispatcher.error = True return # Retrieve SignalHead names from JMRI (will be used to populate menus) signalHeads = InstanceManager.signalHeadManagerInstance( ).getSystemNameList() AutoDispatcher.signalHeadNames = [""] for s in signalHeads : # Use UserName (if available), otherwise SystemName signalName = InstanceManager.signalHeadManagerInstance( ).getSignalHead(s).getUserName() if signalName == None or signalName.strip() == "" : signalName = s AutoDispatcher.signalHeadNames.append(signalName) AutoDispatcher.signalHeadNames.sort() # Retrieve List of SignalHeads that have an icon on the panel nIcons = self.layoutEditor.signalHeadImage.size() for i in range(nIcons) : signalIcon = self.layoutEditor.signalHeadImage.get(i) signalHead = signalIcon.getSignalHead() if signalHead != None : signalName = signalHead.getUserName() if signalName == None : signalName = signalHead.getSystemName() AutoDispatcher.signalIcons.append(signalName) # Keep note of original signal icon click mode AutoDispatcher.signalClick.append( [signalIcon, signalIcon.getClickMode()]) # Retrieve sections from JMRI sections = InstanceManager.sectionManagerInstance().getSystemNameList() if sections.size() < 1 : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Layout contains no sections, script" + " cannot continue!") # Create section and block instances for section in sections : ADsection(section) if len(ADsection.getList()) == 0 : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "No valid section found, script" + " cannot continue!") AutoDispatcher.error = True return # Create entries (connections between sections) for section in ADsection.getList() : section.setEntries() # Retrieve paths (connections between blocks) for block in ADblock.getList() : block.setPaths() # Retrieve Crossings and establish inter-dependecy between # crossed sections nXings = self.layoutEditor.xingList.size() for i in range(nXings) : xing = self.layoutEditor.xingList.get(i) # Ignore crossing if crossed blocks are not included in sections blockAC = ADblock.getByName(xing.blockNameAC) if blockAC != None : blockBD = ADblock.getByName(xing.blockNameBD) if blockBD != None : sectionAC = blockAC.getSection() sectionBD = blockBD.getSection() # Ignore crossing if both crossed bocks belong to # the same section # (results can, however, be unpredictable!) if sectionAC != sectionBD : sectionAC.addXing(sectionBD) sectionBD.addXing(sectionAC) # Retrieve double crossovers by scanning all tunouts # contained in LayoutEditor turnoutsNumber = self.layoutEditor.turnoutList.size() for i in range(turnoutsNumber) : layoutTurnout = self.layoutEditor.turnoutList.get(i) # If the turnout is a double crossover, process it if layoutTurnout.getTurnoutType() == AutoDispatcher.DOUBLE_XOVER : ADxover(layoutTurnout) # Retrieve Layout Editor tracks and save their original width nTracks = self.layoutEditor.trackList.size() if nTracks < 1 : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Warning: layout contains no tracks!") else : for i in range(nTracks) : track = self.layoutEditor.trackList.get(i) blockName = track.getBlockName() block = ADblock.getByName(blockName) # Process track only if block contained in some section if block != None : block.addTrack(ADtrack(track)) # Reorganize sections' directions # Blocks within each section are listed in arbitrary order. # Make sure that the order is consistent. # Starting from the first section, mark sections with inconsistent # order as "reversed" self.processedSections = [] # If all sections are connected, the following for-loop # is actually performed ony once for section in ADsection.getList() : self.__alignSection__(section, False) # Find and mark reversing tracks (if any) self.findTransitPoints() # INTERNAL METHODS OF getLayoutData ============== def __alignSection__(self, section, changeOrientation) : # Internal method. Recursively aligns all sections # If section already processed, exit if section in self.processedSections : return # Take note that this section was processed (in order to avoid a loop!) self.processedSections.append(section) # Change orientation, if needed if changeOrientation : section.setReversed(not section.isReversed()) # Scan both entries and exits for direction in [False, True] : for entry in section.getEntries(direction) : nextSection = entry.getExternalSection() # Adjust orientation of each entry/exit self.__alignSection__(nextSection, self.__hasOrientationChanged__(entry, direction)) def findTransitPoints(self) : # Find transit points on possible reversing tracks # Train color will change when transiting these points for section in ADsection.getList() : # Scan both entries and exits for direction in [False, True] : for entry in section.getEntries(direction) : change = self.__hasOrientationChanged__(entry, direction) entry.setDirectionChange(change) def __hasOrientationChanged__(self, startEntry, direction) : # Internal method. # Checks if two sections are aligned, by verifying # if the start section is included in the entries of # destination section (keeping into account direction) startSection = startEntry.getInternalSection() startBlock = startEntry.getInternalBlock() endSection = startEntry.getExternalSection() endBlock = startEntry.getExternalBlock() for endEntry in endSection.getEntries(not direction) : if (endEntry.getExternalSection() == startSection and endEntry.getExternalBlock() == startBlock and endEntry.getInternalBlock() == endBlock) : # section found, directions are consistent return False # section not found, directions are inconsistent return True # DEFAULT SETTINGS ============== def defaultSettings(self) : # Set sections' settings to default value # They may then be overriden by user (or by settings stored on disk) # Mark transit-only sections # (Sections where trains cannot stop without blocking traffic) for section in ADsection.getList() : section.setDefault() # USER SETTINGS ============== def userSettings(self): # Override default settings with user defined settings loaded from disk # (or embedded settings for demo panels) # First of all define direction names, based on user choices if ADsettings.ccwStart.strip() != "" : self.setDirections() self.setSignals() # Set user defined one-way and transit-only sections ADsection.putSectionsTable() # Adjust entry points (in case some sections were manually flipped) self.findTransitPoints() # Mark sections where gridlock situations can occur ADgridGroup.create() # Set manual indicator sensors to INACTIVE, otherwise they can be # confused with signals (owing to the red aspect) for section in ADsection.getList() : if not section.isManual() : section.setManual(False) # Set user defined brake, stop, assignment and safe-point blocks ADsection.putBlocksTable() # Retrieve info from JMRI roster and create locomotives with # standard speeds AutoDispatcher.log("Getting JMRI locomotives roster") jmriRoster = Roster.instance().matchingList(None, None, None, None, None, None, None) for i in range(jmriRoster.size()) : name = jmriRoster.get(i).getId() address = int(jmriRoster.get(i).getDccAddress()) ADlocomotive(name, address, None, True) # Roster contains also long address indicator # We should likely use it but, getThrottle method # of AbstractAutomaton don't cares! # Get consists from JMRI # Any consist must be defined before starting AutoDispatcher consistMan = InstanceManager.consistManagerInstance() consists = consistMan.getConsistList() # Any consist? if len(consists) == 0 : # No, make sure consist file was loaded consistFrame = ConsistToolFrame() # and retry consists = consistMan.getConsistList() consistFrame.dispose() for a in consists : c = consistMan.getConsist(a) # Get consist address a = a.getNumber() # Get consist ID i = c.getConsistID() # Create locomotive l = ADlocomotive(i, a, None, True) # Get address of first locomotive in consist ll = c.getConsistList() if len(ll) > 0 : f = ll[0].getNumber() if f != a : # Record address of first locomotive # Function commands will be sent to it. # May need revising. l.leadLoco = f # Match jmri roster with user defined locomotives (and consists) and set # relevant speeds for l in AutoDispatcher.locomotives : ll = ADlocomotive.getByName(l[0]) if ll == None : ll = ADlocomotive(l[0], l[1], l[2], False) else : ll.setSpeedTable(l[2]) ll.setMomentum(l[3], l[4]) ll.runningTime = l[5] ll.mileage = l[6] # Now build trains roster, based on user settings if len(self.trains) > 0 : AutoDispatcher.log("Placing trains on tracks :-)") ADtrain.buildRoster(self.trains) def setDirections(self) : # Find mapping between internal direction and user defined direction # To this purpose, two sections are specified, the shorter route # between them is assumed to be CCW direction (or whatever name user # chose). Return True if the selection was successful. # Check correctness of section names result = True startSection = ADsection.getByName(ADsettings.ccwStart) if startSection == None : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Unknown start section " + ADsettings.ccwStart + " in direction definition") ADsettings.ccwStart = "" result = False endSection = ADsection.getByName(ADsettings.ccwEnd) if endSection == None : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Unknown end section " + ADsettings.ccwEnd + " in direction definition") ADsettings.ccwEnd = "" result = False if result : # Find route in direction 0 route0 = ADautoRoute(startSection, endSection, 0, False) # Find route in direction 1 route1 = ADautoRoute(startSection, endSection, 1, False) # Which one is shorter? len0 = len(route0.step) len1 = len(route1.step) if len0 == 0 or (len1 != 0 and len1 < len0) : ADsettings.ccw = 1 else : ADsettings.ccw = 0 return result def setSignals(self) : # Set signals at section exits, using direction names to identify # signal heads. # Assume that the name of signal head is "H" + name of the # section + (lower case) direction # Example HS01east is the East exit signal of section S01 # User can override the choice by manually selecting a signal head # or signal mast in the "Sections" window extension = [ADsettings.directionNames[0].lower(), ADsettings.directionNames[1].lower()] if ADsettings.ccw != 0: extension.reverse() # Scan all sections for s in ADsection.getList() : # In both directions for i in range(2) : newName = "H" + s.getName() + extension[i] # Check if a SignalMast or a SignalHead with such a name exists newSignal = ADsignalMast.getByName(newName) newHead = InstanceManager.signalHeadManagerInstance( ).getSignalHead(newName) # Assign a new SignalMast only if it was not yet assigned # or it was automatically created in a previous call # (provided we found a replacement!) Replacing signals # is needed since user could change direction names. if (s.signal[i] == None or (s.signal[i].getName() != newName and not s.signal[i].hasHead() and s.signal[i].inUse < 2 and s.signal[i].signalType == ADsettings.signalTypes[0] and (newSignal != None or newHead != None))) : # If a SignalMast was cretaing using the old direction name # delete it if s.signal[i] != None : s.signal[i].changeUse(-2) # Now assign/create the new signal if newSignal == None : s.signal[i] = ADsignalMast.provideSignal(newName) else : s.signal[i] = newSignal # Take note that the signal is being used s.signal[i].changeUse(1) # Try and find the (optional) sensor to set the section under # manual control if s.manualSensor == None : sensorName = s.getName() + "man" if sensorName in ADsection.sensorNames : s.manualSensor = InstanceManager.sensorManagerInstance( ).getSensor(sensorName) # OPERATIONS ============== def init(self): # Start - Performed each time "Start" button is clicked # Clear number of running trains AutoDispatcher.runningTrains = 0 # Start fast clock listener AutoDispatcher.fastBase.addMinuteChangeListener(AutoDispatcher.fastListener) # Set block occupancy listeners ADblock.setListeners() # Set sections manual control sensors listeners ADsection.setListeners() # Let other methods and classes know that we started AutoDispatcher.loop = True AutoDispatcher.stopped = False # Check the initial occupancy state of sections for section in ADsection.getList() : section.occupied = True section.empty = False section.checkOccupancy() section.occupied = False if not section.empty : section.setOccupied() # Force occupation of sections assigned to trains for train in ADtrain.getList() : for section in train.previousSections : section.empty = not section.occupied section.occupied = True section.allocate(train, train.direction) if (train.destination != train.lastRouteSection and train.lastRouteSection != None) : train.lastRouteSection.allocate(train, train.direction) # Set initial width of tracks for block in ADblock.getList() : block.adjustWidth() # Place/remove train names in jmri Blocks # (only if user enabled this option) if ADsettings.blockTracking : for section in ADsection.getList() : section.changeTrainName() # Set click-mode of panel SignalHeadIcons to "alternate held" # to avoid that user may change signal aspects for s in AutoDispatcher.signalClick : s[0].setClickMode(2) # Set variables for the handle method self.train = 0 # Force updating of LayoutEditor panel AutoDispatcher.repaint = True # Enable user interface buttons (unless errors occurred) if not AutoDispatcher.error : AutoDispatcher.mainFrame.enableButtons(False) if AutoDispatcher.simulation : # Separate thread, advancing trains in simulation mode start_new_thread(self.autoStep, ()) # Separate thread, controlling trains speed start_new_thread(self.speedControl, ()) # Separate thread, controlling locomotives maintenance time start_new_thread(self.maintenanceControl, ()) # Separate thread, blinking signal head icons on panle start_new_thread(self.blinkSignals, ()) # If power is off we start in "paused" mode if ADpowerMonitor.powerOn : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Have fun!") else : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Power Off!") ADpowerMonitor.savePause = True self.stopAll() # BACKGROUND TASK ============== def handle(self): # Handle thread. Runs in background and takes care of trains departure # Exit if there was an error or user clicked "STOP" button if AutoDispatcher.error or not AutoDispatcher.loop : # Cleanup before exiting # Wait for all trains to stop self.waitForStop() # Release engineers and throttles (if any) for t in ADtrain.getList() : if t.engineer != None : t.releaseEngineer() locomotive = t.locomotive if locomotive != None : locomotive.releaseThrottle() # Inform other threads that program is exiting AutoDispatcher.stopped = True # Remove listeners ADblock.removeListeners() ADsection.removeListeners() AutoDispatcher.fastBase.removeMinuteChangeListener(AutoDispatcher.fastListener) # Restore original block colors and track width for block in ADblock.getList() : block.restore() self.layoutEditor.redrawPanel() # Restore original click mode of signalHeadIcons on panel for s in AutoDispatcher.signalClick : s[0].setClickMode(s[1]) # Are we quitting? if AutoDispatcher.exiting : # Yes, give user the possibility of saving settings and # trains position self.saveBeforeExit() else : # Script not quitting yet # Allow user to change settings AutoDispatcher.mainFrame.enableButtons(True) # Allow user to restart operations, unless errors occurred if not AutoDispatcher.error : ADmainMenu.startButton.enabled = True AutoDispatcher.chimeLog(ADsettings.START_STOP_SOUND, "Goodbye!") return 0 # Normal loop # Redraw Layout Editor panel, if needed if AutoDispatcher.repaint : AutoDispatcher.repaint = False self.layoutEditor.redrawPanel() # Start trains that are eligible (unless script is paused) if not AutoDispatcher.paused : # At each iteration we try and start one train if len(ADtrain.trains) > 0 : if self.train >= len(ADtrain.trains) : self.train = 0 ADtrain.trains[self.train].startIfReady() self.train += 1 # Wait for a while, letting other threads do their work self.waitMsec(100) return 1 def waitForStop(self) : # Wait until all trains halt (unless script stopped) if AutoDispatcher.stopped : return # Make sure script is not paused, otherwise we risk to wait forever wasPaused = AutoDispatcher.paused self.resume() # If we are in simulation mode, make sure layout is powered if AutoDispatcher.simulation : AutoDispatcher.powerMonitor.setPower(PowerManager.ON) # Wait only if layout is powered # This can actually result in inconsistent situations, but # we can neither switch power on (causing shorts or collisions) # neither wait here forever! if ADpowerMonitor.powerOn : for t in ADtrain.trains : # Lets wait if locomotive's speed is > 0, or # running indicator is set (if the train is being run # manually or by another script, we may not know # which locomotive is used), or # train is being started right now while (t.running or (t.locomotive != None and t.locomotive.getThrottleSpeed() > 0) or ADtrain.turnoutsBusy) : self.waitMsec(100) # Since train is running, occupation state of blocks and # sections may change and the panel may need redrawing if AutoDispatcher.repaint : AutoDispatcher.repaint = False self.layoutEditor.redrawPanel() # Set script into "pause mode", if it was so when this method was called if wasPaused : self.stopAll() def stopAll(self) : # Stop trains (or remove power) when paused or an error occurs # Ignore, if user disabled "Pause" option if(ADsettings.pauseMode == ADsettings.IGNORE or AutoDispatcher.exiting) : return # Make sure we are not called more than once # (owing to Jython lack of synchronization) if not ADmainMenu.resumeButton.enabled : ADmainMenu.resumeButton.enabled = True ADmainMenu.pauseButton.enabled = False ADmainMenu.stopButton.enabled = False # Did user chose to power-off layout? if(ADsettings.pauseMode == ADsettings.POWER_OFF) : # Stop layout AutoDispatcher.paused = True AutoDispatcher.powerMonitor.setPower(PowerManager.OFF) else : # STOP_TRAINS case for train in ADtrain.getList() : train.pause() AutoDispatcher.paused = True def resume(self) : # Resume script after a pause # Ignore if we were not paused if not AutoDispatcher.paused : return AutoDispatcher.paused = False if ADsettings.pauseMode == ADsettings.POWER_OFF : # Switch power ON AutoDispatcher.powerMonitor.setPower(PowerManager.ON) else : # or restart trains for train in ADtrain.getList() : train.resume() ADmainMenu.resumeButton.enabled = False ADmainMenu.pauseButton.enabled = True ADmainMenu.stopButton.enabled = AutoDispatcher.loop def saveBeforeExit(self) : # Give user the possibility of saving settings and trains position # before quitting # Check if trains settings or position were changed if AutoDispatcher.trainsDirty : # Yes, allow user to save them msg = "Save trains settings and positions before quitting? " if not ADpowerMonitor.powerOn : msg += ("(Warning, power is off and trains position could" + " be inconsistent!)") if (JOptionPane.showConfirmDialog(None, msg, "Confirmation", JOptionPane.YES_NO_OPTION) == 0) : self.saveTrains() self.saveSettings() return # Check if settings were changed if AutoDispatcher.preferencesDirty : # Yes, allow user to save them if (JOptionPane.showConfirmDialog(None, "Save preferences before quitting? ", "Confirmation", JOptionPane.YES_NO_OPTION) == 0) : self.saveSettings() # SPEED CONTROL TASK ============== def speedControl(self) : # Handle to control train speeds # Executed in a separate thread # Loops until the background handle exits while not AutoDispatcher.stopped : # Take current time - Will be used when testing # for stalled trains and to avoid throttle timeout # (on some command stations) currentTime = System.currentTimeMillis() # Check all trains for train in ADtrain.getList() : # Check for train stalled if (train.running and ADsettings.stalledDetection != ADsettings.DETECTION_DISABLED and train.lastMove != -1L and not AutoDispatcher.paused and not AutoDispatcher.simulation) : if (currentTime - train.lastMove > ADsettings.stalledTime) : train.lastMove = -1L if (ADsettings.stalledDetection == ADsettings.DETECTION_PAUSE) : AutoDispatcher.instance.stopAll() AutoDispatcher.chimeLog(ADsettings.STALLED_SOUND, "Train " + train.getName() + " stalled in section \"" + train.section.getName() + "\"") # Take care of speed changes only for trains with locomotive # and throttle locomotive = train.locomotive if locomotive != None and locomotive.throttle != None : # Do nothing if train already running at target speed. # Compare speeds rounding them, to cope with different # precisions in Jython (double) and Java (float) targetSpeed = round(locomotive.targetSpeed, 4) presentSpeed = round( locomotive.throttle.getSpeedSetting(), 4) if presentSpeed < 0 : presentSpeed = 0 if presentSpeed != targetSpeed : # Train not running at target speed # Should we stop train ? if targetSpeed < 0 : # Yes - Stop it immediately! if presentSpeed > 0 : locomotive.throttle.setSpeedSetting(targetSpeed) locomotive.rampingSpeed = 0 locomotive.currentSpeedSwing.setText("0") locomotive.updateMeter() else : # Should we increase or decrease speed? if presentSpeed < targetSpeed : # Acceleration delta = locomotive.accStep else : # Deceleration (adjusted for self-learning) delta = (-locomotive.decStep - locomotive.decAdjustment) # Should we progressively vary speed? if delta == 0 : # No - Apply target speed locomotive.throttle.setSpeedSetting(targetSpeed) locomotive.lastSent = currentTime locomotive.rampingSpeed = targetSpeed if targetSpeed <= 0 : locomotive.updateMeter() locomotive.currentSpeedSwing.setText( str(targetSpeed)) else : # Progressive speed change delta *= ADsettings.speedRamp locomotive.rampingSpeed += delta # Make sure speed is within target if ((locomotive.rampingSpeed > targetSpeed and delta > 0) or (locomotive.rampingSpeed < targetSpeed and delta < 0)) : locomotive.rampingSpeed = targetSpeed # Were we braking in self-learning mode? if locomotive.brakeAdjusting : #Yes, did we reach target (i.e. minimum) # speed? if locomotive.rampingSpeed == targetSpeed : # Inform self-learning method locomotive.learningEnd() # Apply new speed only if change is meaningful locomotive.rampingSpeed = round( locomotive.rampingSpeed,4) # Round values based on number of speed steps # used by the locomotive if (round(float(locomotive.rampingSpeed) * locomotive.stepsNumber) != round(float(presentSpeed) * locomotive.stepsNumber)) : # Meaningful change - apply new speed locomotive.throttle.setSpeedSetting( locomotive.rampingSpeed) locomotive.lastSent = currentTime # Update locomotives window (if open) locomotive.currentSpeedSwing.setText( str(locomotive.rampingSpeed)) # If train stopped, update mileage and # operation hours if targetSpeed <= 0 : locomotive.updateMeter() # If requested, periodically change speed (even if not # needed), in order to avoid time-out. # Some command stations stop a locomotive if it did not # receive commands for a while. if (locomotive.rampingSpeed > 0 and ADsettings.maxIdle > 0 and currentTime - locomotive.lastSent > ADsettings.maxIdle) : locomotive.throttle.setSpeedSetting( locomotive.rampingSpeed) locomotive.lastSent = currentTime # Wait n/10 of second before repeating # This thread is no time critical, since it only varies speeds # of running trains . # Trains stopping is dealt with by block's ChangeListener sleep(float(ADsettings.speedRamp)*0.1) # LOCOMOTIVES MAINTENANCE TIME CONTROL TASK ============== def maintenanceControl(self) : # Periodically check if any locomotive exceedes the maximum # mileage or operation time while not AutoDispatcher.stopped : # Convert hours to milliseconds intTime = int(ADsettings.maintenanceTime * 3600000.) for l in ADlocomotive.getList() : newWarnedTime = newWarnedMiles = False if (ADsettings.maintenanceTime > 0. and l.runningTime > intTime) : newWarnedTime = True if (ADsettings.maintenanceMiles > 0. and l.mileage > ADsettings.maintenanceMiles) : newWarnedMiles = True # make sure the warning is given only once if (not l.warnedTime and not l.warnedMiles and (newWarnedTime or newWarnedMiles)) : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Locomotive " + l.getName() + " needs maintenance") l.warnedTime = newWarnedTime l.warnedMiles = newWarnedMiles # If mileage and opertions time are being displayed, # update swing fields if AutoDispatcher.locosFrame != None : l.outputMileage() # Not time critical: wait for one minute sleep(60.) def blinkSignals(self) : # Separate thread to blink flashing signals on LayoutEditor panel # (does not affect blinking of model signals on layout) # Quit if there are no Signal Heads on the panel if len(self.layoutEditor.signalHeadImage) == 0 : return # Retrieve signalHeads signals = [] aspectNames = ["SignalHeadStateRed", "SignalHeadStateGreen", "SignalHeadStateYellow", "SignalHeadStateLunar", "SignalHeadStateFlashingRed", "SignalHeadStateFlashingGreen", "SignalHeadStateFlashingYellow", "SignalHeadStateFlashingLunar", "SignalHeadStateDark"] hasLunar = True # Are we running a JMRI version released after 24/9/2010? newIcons = not callable(getattr(self.layoutEditor.signalHeadImage[0], "getRedIcon", None)) if newIcons : # New JMRI version (use getIcon, setIcon) # Get aspect names rbean = java.util.ResourceBundle.getBundle("jmri.NamedBeanBundle") for i in range(len(aspectNames)) : aspectNames[i] = rbean.getString(aspectNames[i]) for s in self.layoutEditor.signalHeadImage : a = [] for i in range(7) : a.append(s.getIcon(aspectNames[i])) signals.append([s, s.getIcon(aspectNames[8]), a]) else: # Old JMRI version (use getRedIcon... setRedIcon...) # Is Lunar aspect (introduced since JMRI 2.7.8) supported? hasLunar = callable(getattr(self.layoutEditor.signalHeadImage[0], "getFlashLunarIcon", None)) for s in self.layoutEditor.signalHeadImage : a = [] a.append([s.getRedIcon(), s.getFlashRedIcon()]) a.append([s.getGreenIcon(), s.getFlashGreenIcon()]) a.append([s.getYellowIcon(), s.getFlashYellowIcon()]) if hasLunar : a.append([s.getLunarIcon(), s.getFlashLunarIcon()]) signals.append([s, s.getDarkIcon(), a]) on = True cycling = False # Loop until handle exits while not AutoDispatcher.stopped : # Did user enable flashing? if ADsettings.flashingCycle > 0 : cycling = True for s in signals : a = s[2] # Light-on half cycle? if on : # Yes - switch on signal head if newIcons : for i in range(3) : if a[i+4] != None : s[0].setIcon(aspectNames[i+4], a[i]) else : s[0].setFlashRedIcon(a[0][0]) s[0].setFlashGreenIcon(a[1][0]) s[0].setFlashYellowIcon(a[2][0]) if hasLunar : s[0].setFlashLunarIcon(a[3][0]) else : # No - set signal head to dark aspect if newIcons : for i in range(3) : if a[i+4] != None : s[0].setIcon(aspectNames[i+4], s[1]) else: s[0].setFlashRedIcon(s[1]) s[0].setFlashGreenIcon(s[1]) s[0].setFlashYellowIcon(s[1]) if hasLunar : s[0].setFlashLunarIcon(s[1]) # Force panel redrawing AutoDispatcher.repaint = True # Invert cycle on = not on # Wait for half cycle sleep(ADsettings.flashingCycle * 0.5) continue # User has disabled flashing # Was flashing enabled before? elif cycling > 0 : # Yes - take note we disable it cycling = False # restore flashing icon in signal heads for s in signals : a = s[2] if newIcons : for i in range(3) : if a[i+4] != None : s[0].setIcon(aspectNames[i+4], a[i+4]) else: s[0].setFlashRedIcon(a[0][1]) s[0].setFlashGreenIcon(a[1][1]) s[0].setFlashYellowIcon(a[2][1]) if hasLunar : s[0].setFlashLunarIcon(a[3][1]) # Force panel redrawing AutoDispatcher.repaint = True # Wait a second sleep(1.0) # Exiting thread # Restore original aspects for s in signals : a = s[2] if newIcons : for i in range(3) : if a[i+4] != None : s[0].setIcon(aspectNames[i+4], a[i+4]) else: s[0].setFlashRedIcon(a[0][1]) s[0].setFlashGreenIcon(a[1][1]) s[0].setFlashYellowIcon(a[2][1]) if hasLunar : s[0].setFlashLunarIcon(a[3][1]) # Force panel redrawing AutoDispatcher.repaint = True # I/O ================= # Data are stored as Java objects. # Saving them as XML file would be nicer, but would # require adding new DTD files in JMRI def saveSettings(self) : # Save settings to disk # Get current settings newSections = ADsection.getSectionsTable() newBlocks = ADsection.getBlocksTable() # Compare current settings with those # automatically computed by the program at startup for i in range(len(newSections)) : newS = newSections[i] autoS = self.autoSections[i] for j in range(1, 3) : # Is user selection different from settings computed by program? if newS[j] != autoS[j] : # Yes - if new settings are "No selection", # set value to "NO" if newS[j] == "" : newS[j] = "NO" for i in range(len(newBlocks)) : newS = newBlocks[i] autoS = self.autoBlocks[i] for j in range(1, len(autoS)) : if j == 6 or j == 12 : continue # Is user selection different from settings computed by program? if newS[j] != autoS[j] : # Yes - if new settings are "No selection", # set value to "NO" if newS[j] == "" : newS[j] = "NO" # Write to file try : outs=ObjectOutputStream(FileOutputStream(self.settingsFile)) try : ADsettings.save(outs, newSections, newBlocks) ADmainMenu.saveSettingsButton.enabled = False AutoDispatcher.preferencesDirty = False AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Preferences saved to disk") finally : outs.close() except IOException, ioe : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Error writing to file " + self.settingsFile) def loadSettings(self) : # Get settings from disk ins = None try : ins=ObjectInputStream(FileInputStream(self.settingsFile)) try : ADsettings.load(ins.readObject()) except IOException, ioe : AutoDispatcher.log("Warning, could not read file " + self.settingsFile + ". Default layout settings will be used.") ins.close() ins = None except IOException, ioe : if ins != None : ins.close() def saveLocomotives(self) : locomotives = [] locos = ADlocomotive.getNames() locos.sort() # Extract persistent info for l in locos : ll = ADlocomotive.getByName(l) locomotives.append([ll.name, ll.address, ll.speed, ll.acceleration, ll.deceleration, ll.runningTime, ll.mileage]) # Write to file try : outs=ObjectOutputStream(FileOutputStream(self.locoFile)) try : outs.writeObject(locomotives) AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Locomotives changes saved to disk") finally : outs.close() except IOException, ioe : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Error writing file " + self.locoFile) def loadLocomotives(self) : try : ins=ObjectInputStream(FileInputStream(self.locoFile)) try : newLocomotives=ins.readObject() AutoDispatcher.locomotives = newLocomotives except IOException, ioe : AutoDispatcher.log("Warning, could not read file " + self.locoFile + " Default settings will be used.") ins.close() except IOException, ioe : return def saveTrains(self) : # First of all, save locomotives self.saveLocomotives() trains = [] # Extract persistent info for t in ADtrain.getList() : if t.direction == ADsettings.ccw : direction = ADsettings.directionNames[0] else : direction = ADsettings.directionNames[1] # Force saving of schedule status in the stack # (This is why trains must be stopped before saving!) t.schedule.push() # Copy stack contents stack = [] stack.extend(t.schedule.stack) # Restore original stack contents t.schedule.pop() # Get current section name if t.section != None : sectionName = t.section.getName() else : sectionName = "" # Get pending commands (if any) pendingCommands = [] for i in range(len(t.itemSections)) : section = t.itemSections[i].getName() action = t.items[i].action value = t.items[i].value message = t.items[i].message # Convert instances (sections or signals) to names if (action == ADschedule.WAIT_FOR or action == ADschedule.MANUAL_OTHER or action == ADschedule.HELD or action == ADschedule.RELEASE or action == ADschedule.IFH) : value = value.getName() pendingCommands.append([section, action, value, message]) # Get name of last section on the route if t.lastRouteSection == None : lastSection = "" else : lastSection = t.lastRouteSection.getName() # Add a record trains.append([t.name, sectionName, direction, t.resistiveWheels, t.schedule.text, t.trainAllocation, t.trainLength, stack, t.locoName, t.reversed, pendingCommands, t.engineerName, lastSection, t.trainSpeed, t.brakingHistory, t.canStopAtBeginning, t.startAction]) # Write everything to file try : outs=ObjectOutputStream(FileOutputStream(self.trainFile)) try : outs.writeObject(trains) AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Trains status saved to disk") finally : outs.close() except IOException, ioe : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Error writing to file " + self.trainFile) ADmainMenu.saveTrainsButton.enabled = AutoDispatcher.trainsDirty = False def loadTrains(self) : # Read train data into memory # Appropriate data conversions will be performed later by # ADtrain.buildRoster method try : ins=ObjectInputStream(FileInputStream(self.trainFile)) try : newTrains=ins.readObject() self.trains = newTrains except IOException, ioe : AutoDispatcher.log("Warning, could not read file " + self.trainFile + ", Default train settings will be used.") ins.close() except IOException, ioe : return # SIMULATION ================= # The following methods are used only in simulation mode def oneStep(self) : # Advance every train of one block, unless it # reached the stop block in front of a red signal. # Ignore calls if stopped, paused or without power if (AutoDispatcher.stopped or AutoDispatcher.paused or not ADpowerMonitor.powerOn) : return # Move running trains one block ahead for t in ADtrain.getList() : # Did train advance during previous call? if t.simSensor != None : # Yes, remove it from previous block t.simSensor.setKnownState(INACTIVE) t.simSensor = None continue # No - Is train running? if (not t.running or (t.locomotive != None and t.engineerSetLocomotive != None and t.locomotive.getThrottleSpeed() <= 0)) : continue # Is the train anywhere? section = t.section direction = t.direction if section != None : # Yes, find present block blocks = section.getBlocks(direction) ind = -1 for i in range(len(blocks)) : if blocks[i].getOccupancy() == Block.OCCUPIED : ind = i break if ind < 0 : continue # Did train reach a stop block in front of a red signal? if (blocks[ind] == section.stopBlock[direction] and section.getSignal(direction).getIndication() == 0) : continue # No - get sensor of current block currentSensor = blocks[ind].getOccupancySensor() # Is this block section's exit block? block = None for e in t.entriesAhead : if e.getInternalBlock() == blocks[ind] : # Yes - Get entry block in the next section block = e.getExternalBlock() while e in t.entriesAhead : t.entriesAhead.pop(0) break if block == None : # Train did not reach the exit block # Get next block in the same section ind += 1 # Should not occur, but it's better to check if ind >= len(blocks) : continue block = blocks[ind] # Get next sensor nextSensor = block.getOccupancySensor() # Move train from current block to next one, # making sure the block has a sensor! if nextSensor != currentSensor and nextSensor != None : nextSensor.setKnownState(ACTIVE) # Take note that next time we must release the old block t.simSensor = currentSensor def autoStep(self) : # Step trains forward every two seconds while not AutoDispatcher.stopped : # Call "oneStep" once per second # actual advancement will occur once every two seconds, # since during one call next block is occupied # and during next call previous block is released sleep(1.0) if ADmainMenu.autoButton.isSelected() : self.oneStep() # CHILDREN CLASSES ============== # SECTION ============== class ADsection (PropertyChangeListener) : # Encapsulates JMRI Section, adding fields and methods used # by AutoDispatcher # Section colors enumeration EMPTY_SECTION_COLOR = 0 MANUAL_SECTION_COLOR = 1 OCCUPIED_SECTION_COLOR = 2 CCW_ALLOCATED_COLOR = 3 CW_ALLOCATED_COLOR = 4 CCW_TRAIN_COLOR = 5 CW_TRAIN_COLOR = 6 # Static variables userNames ={} # dictionary of sections by userName systemNames ={} # dictionary of sections by systemName sensorNames = None # list of sensors that may be used for manual control # (i.e. those not used for block occupancy) # STATIC METHODS def getList() : return ADsection.systemNames.values() getList = ADstaticMethod(getList) def getNames() : return ADsection.userNames.keys() getNames = ADstaticMethod(getNames) def getByName(name) : if ADsection.userNames.has_key(name) : return ADsection.userNames[name] return ADsection.systemNames.get(name, None) getByName = ADstaticMethod(getByName) def getSectionsTable() : # Creates a table containing sections' attributes as strings. # Uses current settings in order to translate direction names. # The output table, sorted by section name, contains: # Section Name # One-Way Indicator # Transit-Only (and burst) Indicator # Signal names # Manual flipping Indicator # Name of manual control sensor # Signal held indicators out = [] for s in ADsection.systemNames.values() : if s.direction == 3 : direction = "" elif s.direction == ADsettings.ccw + 1 : direction = ADsettings.directionNames[0] else : direction = ADsettings.directionNames[1] if s.burst : burst = "+" else : burst = "" if s.transitOnly[ADsettings.ccw] : if s.transitOnly[1-ADsettings.ccw] : transitOnly = (ADsettings.directionNames[0] + "-" + ADsettings.directionNames[1] + burst) else : transitOnly = ADsettings.directionNames[0] + burst elif s.transitOnly[1-ADsettings.ccw] : transitOnly = ADsettings.directionNames[1] + burst else : transitOnly = "" signals =["", ""] heldIndicators =["", ""] for i in range(2) : if s.signal[i] != None : signals[i] = s.signal[i].name if s.signal[i].isHeld() : heldIndicators[i] = "Held" if s.manuallyFlipped : inverted = "INVERTED" else : inverted = "" if s.isManual() : manualSection = "Manual" else : manualSection = "" if s.manualSensor == None : manualSensor = "" else : manualSensor = s.manualSensor.getUserName() if manualSensor == None or manualSensor == "" : manualSensor = s.manualSensor.getSystemName() out.append([s.name, direction, transitOnly, signals[0], signals[1], inverted, manualSensor, s.stopAtBeginning, heldIndicators, manualSection]) out.sort() return out getSectionsTable = ADstaticMethod(getSectionsTable) def putSectionsTable() : # Updates sections, based on a table of attributes in string format. # Uses as input the table in the format provided by the # getSectionsTable method and contained in current settings for i in ADsettings.sections : section = ADsection.getByName(i[0]) if section != None : if i[1] == "NO" : section.direction = 3 elif (i[1] == "CCW" or i[1] == "EAST" or i[1] == "NORTH" or i[1] == "LEFT" or i[1] == "UP") : section.direction = ADsettings.ccw + 1 section.transitOnly[ADsettings.ccw] = False elif (i[1] == "CW" or i[1] == "WEST" or i[1] == "SOUTH" or i[1] == "RIGHT" or i[1] == "DOWN") : section.direction = 2 - ADsettings.ccw section.transitOnly[1-ADsettings.ccw] = False i2 = i[2] if i2.endswith("+") : i2 = i2[:len(i2)-1] burst = True else : burst = False if i2 == "NO" : section.transitOnly[0] = section.transitOnly[1] = False section.burst = False elif (i2 == "CCW-CW" or i2 == "EAST-WEST" or i2 == "NORTH-SOUTH" or i2 == "LEFT-RIGHT" or i2 == "UP-DOWN") : section.transitOnly[0] = section.transitOnly[1] = True section.burst = burst elif (i2 == "CCW" or i2 == "EAST" or i2 == "NORTH" or i2 == "LEFT" or i2 == "UP") : section.transitOnly[ADsettings.ccw] = True section.transitOnly[1-ADsettings.ccw] = False section.burst = burst elif (i2 == "CW" or i2 == "WEST" or i2 == "SOUTH" or i2 == "RIGHT" or i2 == "DOWN") : section.transitOnly[1-ADsettings.ccw] = True section.transitOnly[ADsettings.ccw] = False section.burst = burst for j in range(2) : if (i[j+3] != "" and (section.signal[j] == None or i[j+3] != section.signal[j].name)) : if section.signal[j] != None : section.signal[j].changeUse(-2) section.signal[j] = ADsignalMast.provideSignal(i[j+3]) section.signal[j].changeUse(1) if i[5] == "INVERTED" : section.manuallyFlip() sensorName = i[6] if sensorName == "NO" : section.manualSensor = None elif sensorName.strip() != "" : section.manualSensor = ( InstanceManager.sensorManagerInstance( ).getSensor(sensorName)) if len(i) > 7 : section.stopAtBeginning = i[7] if len(i) > 9 and ADsettings.autoRestart : for j in range(2) : if (i[8][j] == "Held" and section.signal[j] != None and section.signal[j].hasIcon()) : section.signal[j].setHeld(True) if i[9] == "Manual": section.setManual(True) putSectionsTable = ADstaticMethod(putSectionsTable) def getBlocksTable() : # Creates a table containing blocks' attributes as strings. # The output table, sorted by section name, contains: # Block Name # Stop-Block Indicator # Allocation-Block Indicator # Safe-Point Indicator # Maximum speed name # Brake-Block Indicator # List of block actions for each direction # Within the section, blocks are sorted in accordance to # internal direction out = [] sections = ADsection.getNames() sections.sort() for sectionName in sections : section = ADsection.getByName(sectionName) for block in section.getBlocks(True) : outData = [block.getName()] for j in range (2) : if block == section.stopBlock[j]: outData.append("STOP") else : outData.append("") if block == section.allocationPoint[j]: outData.append("ALLOCATE") else : outData.append("") if block == section.safePoint[j]: outData.append("SAFE") else : outData.append("") speedIndex = block.getSpeed(j) if speedIndex == 0 : outData.append("") else : outData.append( ADsettings.speedsList[speedIndex-1]) if block == section.brakeBlock[j]: outData.append("BRAKE") else : outData.append("") outData.append(block.action[j]) out.append(outData) return out getBlocksTable = ADstaticMethod(getBlocksTable) def putBlocksTable() : # Updates blocks, based on a table of attributes in string format. # Uses as input the table in the format provided by the # getBlocksTable method and contained in current settings for i in ADsettings.blocks : block = ADblock.getByName(i[0]) if block != None : section = block.getSection() jj = 1 for j in range(2) : if i[jj] == "STOP" : section.stopBlock[j] = block jj +=1 if i[jj] == "ALLOCATE" : section.allocationPoint[j] = block jj +=1 if i[jj] == "SAFE" : section.safePoint[j] = block elif i[jj] == "NO" and section.safePoint[j] == block : section.safePoint[j] = None jj +=1 speedName = i[jj] for speedIndex in range(len(ADsettings.speedsList)) : if (speedName == ADsettings.speedsList[speedIndex]) : block.speed[j] = speedIndex + 1 break jj +=1 if i[jj] == "BRAKE" : section.brakeBlock[j] = block jj +=1 block.action[j] = i[jj] jj +=1 putBlocksTable = ADstaticMethod(putBlocksTable) def setListeners() : # Add a property change listener for each section that has a sensor # for manual control for section in ADsection.systemNames.values() : if(section.manualSensor != None) : # add the listener section.manualSensor.addPropertyChangeListener(section) setListeners = ADstaticMethod(setListeners) def removeListeners() : # Remove the manual control sensor listener of each section for section in ADsection.systemNames.values() : if(section.manualSensor != None) : # remove the listener section.manualSensor.removePropertyChangeListener(section) removeListeners = ADstaticMethod(removeListeners) # INSTANCE METHODS def __init__(self, systemName): # Retrieve Section.java instance from JMRI self.jmriSection = ( InstanceManager.sectionManagerInstance().getBySystemName(systemName)) # Get section name self.name = self.jmriSection.getUserName() if self.name == None or self.name.strip() == "" : self.name = systemName self.name = AutoDispatcher.cleanName(self.name) # Swing variables used in Blocks window self.stopGroup = [ButtonGroup(), ButtonGroup()] self.brakeGroup = [ButtonGroup(), ButtonGroup()] self.brakeNoneSwing = [JRadioButton(""), JRadioButton("")] self.brakeNoneSwing[0].setHorizontalAlignment(JLabel.CENTER) self.brakeNoneSwing[1].setHorizontalAlignment(JLabel.CENTER) self.brakeGroup[0].add(self.brakeNoneSwing[0]) self.brakeGroup[1].add(self.brakeNoneSwing[1]) self.safeGroup = [ButtonGroup(), ButtonGroup()] self.safeNoneSwing = [JRadioButton(""), JRadioButton("")] self.safeNoneSwing[0].setHorizontalAlignment(JLabel.CENTER) self.safeNoneSwing[1].setHorizontalAlignment(JLabel.CENTER) self.safeGroup[0].add(self.safeNoneSwing[0]) self.safeGroup[1].add(self.safeNoneSwing[1]) self.allocationGroup = [ButtonGroup(), ButtonGroup()] # Find out all blocks contained in the section # Make sure JMRI section direction is forward! # Otherwise we will get the list in reversed order. self.blockList = [] # Blocks contained in the section self.jmriSection.setState(Section.FREE) newBlock = self.jmriSection.getEntryBlock() while newBlock != None : # Get the corresponding LayoutBlock # Without LayoutBlock we cannot setup connectivity layoutBlock = None blockName = newBlock.getUserName() if blockName != None and blockName.strip() != "" : layoutBlock = InstanceManager.layoutBlockManagerInstance( ).getLayoutBlock(blockName) if layoutBlock == None : blockName = newBlock.getSystemName() layoutBlock = InstanceManager.layoutBlockManagerInstance( ).getLayoutBlock(blockName) if layoutBlock == None : AutoDispatcher.log("No LayoutBlock for Block " + blockName + " found: block skipped!") else : # make sure that block is included in one section only block = ADblock.getByName(blockName) if block != None : AutoDispatcher.log("Block " + blockName + " included in both " + block.getSection().getName() + " and " + self.name + " sections. AutoDispatcher " + "is unable to handle this case.") # Continue even if results are unpredictable # Add the layoutBlock as sub-block self.blockList.append(ADblock(self, layoutBlock, blockName)) newBlock = self.jmriSection.getNextBlock() # Make sure section contains at least one block blocksNumber = len(self.blockList) if blocksNumber < 1 : AutoDispatcher.log("Section " + self.name + "contains no valid block. Section skipped!") return # Update the maximum number of blocks per section (will be used to # determine default settings) if AutoDispatcher.maxBlocksPerSection < blocksNumber : AutoDispatcher.maxBlocksPerSection = blocksNumber # Include names in dictionaries ADsection.systemNames[AutoDispatcher.cleanName(systemName)] = self ADsection.userNames[self.name] = self self.forwardEntries=[] # Entries self.reverseEntries=[] # Exits self.xings = [] # Sections crossed by this section self.stopBlock = [None, None] self.brakeBlock = [None, None] self.allocationPoint = [None, None] self.safePoint = [None, None] self.manualSensor = None self.stopAtBeginning = [-1.0, -1.0] # Assume that order of blocks and entries is not reversed self.reversed = False # Assume that user did not flip yet the order of blocks self.manuallyFlipped = False # Signals at the two exits of the section self.signal = [None, None] # Permitted train direction self.direction = 3 # 3 = both # Trains can stop self.transitOnly = [False, False] # "Burst" mode on transit-ony sections disabled self.burst = False # Section does not contain yet the head of a train self.trainHead = False # Section is not occupied yet self.occupied = False # Section is not allocated yet self.allocated = None # Section length. The value si recomputed each time a train travels # through the section, since actual length depends upon the entry and # exit points the train comes thru self.sectionLength = 0.0 # Allocated train direction in this block self.trainDirection = 0 # Indicator of exit turnouts position # True if at least one turnout is thrown self.turnoutsThrown = False # Retrieve the list of sensor names from JMRI, unless already done. # It will be used to populate sensor combo boxes if ADsection.sensorNames == None : ADsection.sensorNames = [""] # Remove from the list sensors associated with blocks # (this makes user selection easier and faster) # Get all blocks blocks = InstanceManager.blockManagerInstance().getSystemNameList() blockSensors = [] for b in blocks : sensor = InstanceManager.blockManagerInstance( ).getBlock(b).getSensor() if sensor != None : blockSensors.append(sensor.getSystemName()) sensors = InstanceManager.sensorManagerInstance( ).getSystemNameList() for s in sensors: if not s in blockSensors : sensorName = InstanceManager.sensorManagerInstance( ).getSensor(s).getUserName() if sensorName == None or sensorName.strip() == "" : sensorName = s if sensorName != "ISCLOCKRUNNING" : ADsection.sensorNames.append(sensorName) ADsection.sensorNames.sort() # Swing variables used in Sections window # We should probably move them to the GUI self.oneWaySwing = JComboBox() self.transitOnlySwing = JComboBox() self.signalSwing = [JComboBox(), JComboBox()] self.manualSensorSwing = JComboBox() self.stopAtBeginningSwing = [JCheckBox("", False), JCheckBox("", False)] self.stopAtBeginningDelay = [JTextField("0.0", 4), JTextField("0.0", 4)] def setEntries(self): # Find section's entry points self.forwardEntries = self.__setEntries__( self.jmriSection.getForwardEntryPointList()) self.reverseEntries = self.__setEntries__( self.jmriSection.getReverseEntryPointList()) # Set default stop blocks self.brakeBlock[0] = self.stopBlock[0] = self.__setStopBlocks__( self.getBlocks(False), self.forwardEntries) self.brakeBlock[1] = self.stopBlock[1] = self.__setStopBlocks__( self.getBlocks(True), self.reverseEntries) # Set default allocation blocks and safe points self.safePoint[0] = self.allocationPoint[1] = self.stopBlock[0] self.safePoint[1] = self.allocationPoint[0] = self.stopBlock[1] if len(self.blockList) > 1 : # Set default brake block to block preceding stop block for i in range(2) : found = False for block in self.getBlocks(1-i) : if found : self.brakeBlock[i] = block break found = block == self.stopBlock[i] else : # Section contains only one block. # Brake and safe points are meaningless self.brakeBlock[0] = self.brakeBlock[1] = None self.safePoint[0] = self.safePoint[1] = None def __setEntries__(self, entries) : # Internal method used to retrieve entry points from JMRI entryPoints=[] for i in range(entries.size()) : # Get a JMRI EntryPoint entry = entries.get(i) # Create our own entry instance entryPoint = ADentry(self, entry) # If the creation failed, discard the instance if not entryPoint.inError() : entryPoints.append(entryPoint) entryPoint.getInternalBlock().entryBlock = True return entryPoints def __setStopBlocks__(self, blocks, entries) : # Stop point is set to the first exit block encountered block = None for b in blocks : for e in entries : if b == e.getInternalBlock() : return b block = b return block def addXing(self, otherSection): # Add an item to the list of sections crossed by this section self.xings.append(otherSection) def setDefault(self): # Set transit-only indicators to default values # Any section containing a Xing or only one block # (unless all sections are one block long) is # initially considered as transit-only in both directions # (user can change settings later in "Blocks" window) if (len(self.xings) > 0 or (len(self.blockList) < 2 and AutoDispatcher.maxBlocksPerSection > 1)) : self.transitOnly[0] = self.transitOnly[1] = True def getName(self): return self.name def getJmriSection(self): return self.jmriSection def getEntries(self, backwards): # Return entries (backwards == False) # or exits (backwards == True) # swapping them if order of blocks is reversed if backwards == self.reversed : return self.forwardEntries else : return self.reverseEntries def isReversed (self) : # Is the order of blocks reversed? return self.reversed def setReversed (self, reversed) : # Set the indicator of reversed order of blocks if reversed == self.reversed : return self.reversed = reversed # Flip contents of tables self.stopBlock.reverse() self.brakeBlock.reverse() self.allocationPoint.reverse() self.safePoint.reverse() # Do the same for blocks for block in self.blockList : block.speed.reverse() block.action.reverse() def getSignal(self, direction): if direction < 0 : direction = 0 elif direction > 1 : direction = 1 return self.signal[direction] def getStopAtBeginning(self, direction): if direction < 0 : direction = 0 elif direction > 1 : direction = 1 return self.stopAtBeginning[direction] def manuallyFlip(self) : # Reverse section's direction and take note that user requested flipping # (Can occur only for reversing-sections) self.setReversed(not self.reversed) self.manuallyFlipped = not self.manuallyFlipped def setOccupied(self) : # Set section to occupied (unless already set) if not self.occupied : self.empty = False self.occupied = True # Update section color self.setColor() def checkOccupancy(self) : # If already empty, ignore (shouldn't happen) if not self.occupied and self.allocated == None : return # Clear occupancy status only if all sub-blocks are empty for b in self.blockList : if b.getOccupancy() == Block.OCCUPIED : return # report error if the train "disappeared" if self.trainHead and self.allocated != None : # Avoid the warning when user is allowed to manually perform # switching operations if (not AutoDispatcher.stopped and not AutoDispatcher.paused and not self.isManual() and ADsettings.derailDetection != ADsettings.DETECTION_DISABLED) : if (ADsettings.derailDetection == ADsettings.DETECTION_PAUSE) : AutoDispatcher.instance.stopAll() AutoDispatcher.chimeLog(ADsettings.DERAILED_SOUND, "Train \"" + self.allocated.getName() + "\" derailed in section \"" + self.name + "\"") self.allocated.lastMove = -1L return # Now this section is empty self.empty = True # Release it if train reached the "safe point" in next section self.freeSectionIfTrainSafe() def freeSectionIfTrainSafe(self) : # Relase the section if : # section is empty; # and # train is equipped with resistive wheels # or # the distance of train head from section boundary # is greater than train length # or # train reached the safe-point of next section train = self.allocated if not self.occupied and train == None : # Already released (can occur if sensor is re-triggered) return # Is section allocated to a train? if train != None : # Is train's tail in this section? if (len(train.previousSections) > 0 and train.previousSections[0] != self) : # No - Try releasing previous sections (if empty) previousCount = 0 while True : # Recursivelly call "freeSectionIfTrainSafe" while # sections continue being released currentCount = len(train.previousSections) if currentCount == 0 or currentCount == previousCount : break previousCount = currentCount train.previousSections[0].freeSectionIfTrainSafe() return # Yes, section contains train's tail - Is train using # resistive wheels? if not train.resistiveWheels : # No - Did train reach the safe point? if not train.safe : # No - Can we release the section based on train's length? if (not ADsettings.useLength or train.trainLength <= 0.) : # No - We can thus not release the section return # Yes - See if the head of the train is far enough if train.trainLength > train.distance : # Not yet return if not self.empty : # At least one block sensor still active # Test for lost cars if (ADsettings.lostCarsDetection == ADsettings.DETECTION_DISABLED or AutoDispatcher.paused or AutoDispatcher.stopped or train == None) : return # Can we base detection of lost cars on train length? if (ADblock.blocksWithoutLength == 0 and train.trainLength > 0.) : # Yes, is the head of the train not too far from the tail? # (Let's introduce some tollerance to compensate # for sensors debouncing) if (train.distance - train.trainLength < ADsettings.lostCarsTollerance) : # Not too far return else : # No length available # Let's base detection on the number of sections # not yet released if (len(train.previousSections) <= ADsettings.lostCarsSections) : # Not too far return if (ADsettings.lostCarsDetection == ADsettings.DETECTION_PAUSE) : AutoDispatcher.instance.stopAll() AutoDispatcher.chimeLog(ADsettings.LOST_CARS_SOUND, "Train \"" + train.getName() + "\" lost cars in section \"" + self.name + "\"") return # One way or another we found that we can release the section self.occupied = False if train != None : # Update distance of train head from section boundary if len(train.previousSections) < 3 : # If train is in the next section, clear distance # We cannot release next section while the train is in it! train.distance = 0. train.blockLength = 0. else : # If train head is two or more sections ahead # decrease total distance by present section length train.distance -= self.sectionLength if train.distance < 0. : train.distance = 0. train.blockLength = train.block.getLength() # Remove section from the list of train's passed sections (if any) while self in train.previousSections : train.previousSections.pop(0) # Take note that the section is not allocated any more self.allocate(None, 0) # Reset signals if self.signal[0] != None : self.signal[0].setIndication(0) if self.signal[1] != None : self.signal[1].setIndication(0) # Update section color self.setColor() # Should train stop at the start of next section? if train == None or not train.shouldStopAtBeginning() : return if(len(train.previousSections) > 0 and train.previousSections[0] != train.section) : return # Yes, stop train start_new_thread(self.stopTrain, (train, train.section.stopAtBeginning[train.direction],)) def stopTrain(self, train, delay) : # Stop a train after an optional delay if delay > 0 : sleep(delay) # Make sure train is still braking if train.speedLevel != 1 : return train.arrived() if (train.locomotive == None or train.engineerSetLocomotive == None or AutoDispatcher.simulation) : train.stop() if train.locomotive != None : train.locomotive.learningStop() train.changeSpeed(0) def isOccupied (self) : return self.occupied def allocate(self, train, direction) : # Called when: # section is allocated to a new train # section is de-allocated # train direction changed # Update JMRI section state, if requested if ADsettings.sectionTracking : if self.trainDirection != direction or self.allocated != train : if train == None : if self.allocated != None : self.jmriSection.setState(Section.FREE) else : if direction == self.reversed : self.jmriSection.setState(Section.REVERSE) else : self.jmriSection.setState(Section.FORWARD) self.trainDirection = direction # If only direction changed, return if self.allocated == train : return self.trainHead = train != None and self.occupied self.allocated = train # Change train name in blocks (if required) self.changeTrainName() # Clear length. Will be recomputed as the train advances self.sectionLength = 0. self.setColor() def changeTrainName(self) : # Place/remove train name in jmri Blocks # (only if user enabled this option) if not ADsettings.blockTracking : return if self.allocated == None : if self.occupied : for block in self.blockList : block.setValue("") else : if self.occupied : trainName = self.allocated.getName() for block in self.blockList : if block.getOccupancy() == Block.OCCUPIED : block.setValue(trainName) def getAllocated (self) : return self.allocated def isAvailable(self) : # Check if section is available if self.occupied or self.allocated != None : # Section occupied or allocated return False # If the section has crossings, check the status of crossed sections for x in self.xings : if x.isOccupied() or x.getAllocated() != None : return False # Check if section is under manual control return not self.isManual() def isManual(self) : if self.manualSensor == None : return False return self.manualSensor.getKnownState() == Sensor.ACTIVE def setManual(self, on) : # DO it only if user defined a manual control sensor if self.manualSensor != None : if on : self.manualSensor.setKnownState(Sensor.ACTIVE) else : self.manualSensor.setKnownState(Sensor.INACTIVE) def setColor(self) : # Set section color depending on section status # (only if user enabled this option) if AutoDispatcher.stopped or not ADsettings.useCustomColors : return if self.allocated != None : if self.occupied : if not self.allocated.running and self.isManual() : color = ADsettings.sectionColor[ ADsection.MANUAL_SECTION_COLOR] elif self.allocated.getDirection() == ADsettings.ccw : color = ADsettings.sectionColor[ADsection.CCW_TRAIN_COLOR] else : color = ADsettings.sectionColor[ADsection.CW_TRAIN_COLOR] elif self.allocated.getDirection() == ADsettings.ccw : color = ADsettings.sectionColor[ADsection.CCW_ALLOCATED_COLOR] else : color = ADsettings.sectionColor[ADsection.CW_ALLOCATED_COLOR] elif self.isManual() : color = ADsettings.sectionColor[ADsection.MANUAL_SECTION_COLOR] elif self.occupied : color = ADsettings.sectionColor[ADsection.OCCUPIED_SECTION_COLOR] else : color = ADsettings.sectionColor[ADsection.EMPTY_SECTION_COLOR] for block in self.blockList : block.setColor(color) def getBlocks(self, direction) : # Returns the list of blocks contained in the section # sorted in accordance to train direction if direction != self.reversed : return self.blockList l = [] l.extend(self.blockList) l.reverse() return l def updateFromSwing(self) : # Update section settings in accordance to input swing fields direction = self.oneWaySwing.getSelectedItem() transitOnly = self.transitOnlySwing.getSelectedItem() if transitOnly.endswith("+") : burst = True transitOnly = transitOnly[:len(transitOnly)-1] else : burst = False self.direction = 3 if direction == ADsettings.directionNames[0] : self.direction = ADsettings.ccw + 1 elif direction == ADsettings.directionNames[1] : self.direction = 2 - ADsettings.ccw self.transitOnly[0] = self.transitOnly[1] = False self.burst = False if (transitOnly == ADsettings.directionNames[0] or transitOnly == ADsettings.directionNames[0] + "-" + ADsettings.directionNames[1]) : self.transitOnly[ADsettings.ccw] = True self.burst = burst if (transitOnly == ADsettings.directionNames[1] or transitOnly == ADsettings.directionNames[0] + "-" + ADsettings.directionNames[1]) : self.transitOnly[1-ADsettings.ccw] = True self.burst = burst for j in range(2) : newSignalName = self.signalSwing[j].getSelectedItem()[3:] if newSignalName != self.signal[j].name and newSignalName.strip() != "" : if self.signal[j] != None : self.signal[j].changeUse(-1) self.signal[j] = ADsignalMast.provideSignal(newSignalName) self.signal[j].changeUse(1) if self.stopAtBeginningSwing[j].isSelected() : try : self.stopAtBeginning[j] = float(self.stopAtBeginningDelay[j].text) except : self.stopAtBeginning[j] = 0.0 self.stopAtBeginningDelay[j].text = "0.0" else : self.stopAtBeginning[j] = -1.0 self.stopAtBeginningDelay[j].text = "0.0" sensorName = self.manualSensorSwing.getSelectedItem() if sensorName == None : self.manualSensor = None else : self.manualSensor = InstanceManager.sensorManagerInstance( ).getSensor(sensorName) def propertyChange(self, event) : # Listener, invoked when the Manual sensor status changes if (event.getPropertyName() != "KnownState" or event.newValue == event.oldValue) : return if (event.newValue == Sensor.ACTIVE or event.newValue == Sensor.INACTIVE) : # Status changed self.setColor() # ENTRY POINT ============== class ADentry : # Replaces JMRI class EntryPoint def __init__(self, section, entry): self.internalBlock = None self.externalBlock = None # Should transiting trains invert direction (i.e. color)? self.directionChange = False # Does entry include double crossovers? self.xOver = [] # Error during creation self.error = True blockName = entry.getBlock().getUserName() if blockName == None : blockName = entry.getBlock().getSystemName() self.internalBlock = ADblock.getByName(blockName) # Skip entry if entry block not included in any section if self.internalBlock == None : return # Skip entry if entry block not included in this section # (should not occur!) if self.internalBlock.getSection() != section : return blockName = entry.getFromBlock().getUserName() if blockName == None : blockName = entry.getFromBlock().getSystemName() # Skip entry if external block not included in any section # (it may occur) self.externalBlock = ADblock.getByName(blockName) if self.externalBlock == None : return # Creation successful: clear error flag self.error = False def inError(self): # Did any error occur during instantation? return self.error def getInternalBlock(self): # return the block internal to the section return self.internalBlock def getExternalBlock(self): # return the block external to the section return self.externalBlock def getInternalSection(self): # return the section of this entry return self.internalBlock.getSection() def getExternalSection(self): # return the section connected by this entry return self.externalBlock.getSection() def getDirectionChange(self): # Does train direction change when transiting over this entry? # (e.g. Eastbound trains become Westbound) return self.directionChange def setDirectionChange(self, change): self.directionChange = change def addXover(self,xOver): self.xOver.append(xOver) def areXoversAvailable(self): # Check if all Xovers of the route (if any) are available internalSection = self.internalBlock.getSection() for xOver in self.xOver : if not xOver.isAvailable(internalSection) : return False return True # BLOCK ============== class ADblock (PropertyChangeListener) : # Encapsulates JMRI Block and LayoutBlock, adding fields and methods # used by AutoDispatcher # STATIC VARIABLES userNames ={} # dictionary of blocks by userName systemNames ={} # dictionary of blocks by systemName blocksList = {} # dictionary of blocks by layoutBlock # Keep a count of how many blocks have a defined length and how many # do not have it blocksWithLength = 0 blocksWithoutLength = 0 # STATIC METHODS def getList() : return ADblock.systemNames.values() getList = ADstaticMethod(getList) def getByName(name) : if ADblock.userNames.has_key(name) : return ADblock.userNames[name] return ADblock.systemNames.get(name, None) getByName = ADstaticMethod(getByName) def getByLayoutBlock(layoutBlock) : return ADblock.blocksList.get(layoutBlock, None) getByLayoutBlock = ADstaticMethod(getByLayoutBlock) def setListeners() : # Add a property change listener for each block # In order to avoid race conditions, listeners are added to # JMRI Block.java, not to sensors! for block in ADblock.systemNames.values() : if(block.jmriBlock != None) : # add the listener block.jmriBlock.addPropertyChangeListener(block) setListeners = ADstaticMethod(setListeners) def removeListeners() : # Remove the listener of each block for block in ADblock.systemNames.values() : if(block.jmriBlock != None) : # remove the listener block.jmriBlock.removePropertyChangeListener(block) removeListeners = ADstaticMethod(removeListeners) # INSTANCE METHODS def __init__(self, section, layoutBlock, userName) : # Take note of section (each block must be contained in # one section only!) self.section = section self.layoutBlock = layoutBlock self.jmriBlock = self.layoutBlock.getBlock() self.name = self.layoutBlock.getUserName() if self.name == None or self.name.strip() == "" : self.name = self.layoutBlock.getSystemName() ADblock.userNames[userName] = self ADblock.systemNames[layoutBlock.getSystemName()] = self ADblock.blocksList[layoutBlock] = self # Get block length and take note of how many blocks have it # The later info will be used in the Preferences window self.length = float(self.jmriBlock.getLengthMm()) if self.length <= 0.0 : self.length = 0.0 ADblock.blocksWithoutLength += 1 else : ADblock.blocksWithLength += 1 # We do not know yet if this block is an entry point self.entryBlock = False # Set maximum speeds to undefined self.speed = [0, 0] # Block actions self.action = ["", ""] # Save original block colors in order to restore them before exiting self.trackColor = layoutBlock.getBlockTrackColor() self.occupiedColor = layoutBlock.getBlockOccupiedColor() # Path to other blocks self.paths = {} # Tracks contained in the Block self.tracks = [] # Warn user if block has no sensor if self.getOccupancySensor() == None : AutoDispatcher.log("No sensor for block \"" + self.name + "\" found") # Swing variables. (To be moved to GUI?) self.stopSwing = [JRadioButton(""), JRadioButton("")] self.stopSwing[0].setHorizontalAlignment(JLabel.CENTER) self.stopSwing[1].setHorizontalAlignment(JLabel.CENTER) self.section.stopGroup[0].add(self.stopSwing[0]) self.section.stopGroup[1].add(self.stopSwing[1]) self.brakeSwing = [JRadioButton(""), JRadioButton("")] self.brakeSwing[0].setHorizontalAlignment(JLabel.CENTER) self.brakeSwing[1].setHorizontalAlignment(JLabel.CENTER) self.section.brakeGroup[0].add(self.brakeSwing[0]) self.section.brakeGroup[1].add(self.brakeSwing[1]) self.allocationSwing = [JRadioButton(""), JRadioButton("")] self.allocationSwing[0].setHorizontalAlignment(JLabel.CENTER) self.allocationSwing[1].setHorizontalAlignment(JLabel.CENTER) self.section.allocationGroup[0].add(self.allocationSwing[0]) self.section.allocationGroup[1].add(self.allocationSwing[1]) self.safeSwing = [JRadioButton(""), JRadioButton("")] self.safeSwing[0].setHorizontalAlignment(JLabel.CENTER) self.safeSwing[1].setHorizontalAlignment(JLabel.CENTER) self.section.safeGroup[0].add(self.safeSwing[0]) self.section.safeGroup[1].add(self.safeSwing[1]) self.speedSwing = [JComboBox(), JComboBox()] self.actionSwing = [JTextField(self.action[0], 5), JTextField(self.action[1], 5)] def setPaths(self) : # Create our paths, based on JMRI paths # Our paths are stored in a Jython dictionary, for fast retrieval # (destination block is the key) self.paths = {} jmriPaths = self.getJmriBlock().getPaths() for i in range(jmriPaths.size()) : jmriPath = jmriPaths.get(i) fromName = jmriPath.getBlock().getUserName() if fromName == None : fromName = jmriPath.getBlock().getSystemName() destinationBlock = ADblock.getByName(fromName) if destinationBlock != None : self.paths[destinationBlock] = jmriPath.getSettings() def addTrack(self, track) : # Add a track to the block self.tracks.append(track) def getSection(self) : # return the section containing this block return self.section def getName(self) : return self.name def getLength(self) : return self.length def getJmriBlock(self) : return self.jmriBlock def setValue(self, value) : # Set the value (e.g. train name) of the corresponding JMRI block self.jmriBlock.setValue(value) def getOccupancy(self) : return self.jmriBlock.getState() def getOccupancySensor(self) : return self.layoutBlock.getOccupancySensor() def getSpeed(self, direction) : foundSpeed = self.speed[direction] if foundSpeed > len(ADsettings.speedsList) : foundSpeed = len(ADsettings.speedsList) return foundSpeed def setTurnouts(self, connectingBlock) : # Set turnouts between present block and connecting block # Return: # True if any turnout is "Thrown" # False if all turnouts are "Closed" (or no turnout was found) thrown = False # Check that connection exists if self.paths.has_key(connectingBlock) : # Connection found beanSettings = self.paths[connectingBlock] # Set all turnouts contained in the path for i in range(beanSettings.size()) : beanSetting = beanSettings.get(i) turnout = beanSetting.getBean() position = beanSetting.getSetting() # Take note if turnout must be throw if position == Turnout.THROWN : thrown = True # Operate turnout only if not yet set in the proper position # or if user disabled "Trust turnouts KnownState" indicator if (not ADsettings.trustTurnouts or turnout.getState() != position) : AutoDispatcher.turnoutCommands[turnout] = [ position, System.currentTimeMillis()] turnout.setState(position) # No need of repainting, since LayoutEditor will do it AutoDispatcher.repaint = False # Wait if user specified a delay between turnouts operation if ADsettings.turnoutDelay > 0 : AutoDispatcher.instance.waitMsec( ADsettings.turnoutDelay) return thrown def setColor(self, color): # Set block color self.layoutBlock.setBlockTrackColor(color) self.layoutBlock.setBlockOccupiedColor(color) def adjustWidth (self) : # Set the width of tracks, depending on block occupancy if not ADsettings.useCustomWidth : return occupied = self.layoutBlock.getOccupancy() == Block.OCCUPIED for track in self.tracks : track.setWidth(occupied) def restore(self) : # Restore both block colors and tracks' width (invoked before exiting) # Restore original block colors self.layoutBlock.setBlockTrackColor(self.trackColor) self.layoutBlock.setBlockOccupiedColor(self.occupiedColor) # Restore original tracks' width for track in self.tracks : track.restoreWidth() def propertyChange(self, event) : # Listener, invoked when a train enters/leaves the block if (event.getPropertyName() != "state" or event.newValue == event.oldValue) : return ind = 0 if event.newValue == Block.OCCUPIED : # Train entering the block train = self.section.getAllocated() if train == None : # No train expected self.section.setOccupied() # Section is not allocated - Wrong route? if self.entryBlock and not self.section.isManual() : if ((not AutoDispatcher.stopped) and (not AutoDispatcher.paused) and ADsettings.wrongRouteDetection != ADsettings.DETECTION_DISABLED) : # Check if section has crossings # (this could be a false detection due to a short on # the crossing) wrongRoute = True for x in self.section.xings : if x.getAllocated() != None : wrongRoute = False break if wrongRoute : if (ADsettings.wrongRouteDetection == ADsettings.DETECTION_PAUSE) : AutoDispatcher.instance.stopAll() AutoDispatcher.chimeLog(ADsettings.WRONG_ROUTE_SOUND, "Train entering wrong route in section \"" + self.section.getName() + "\", block \"" + self.name + "\"") else : # Train expected train.changeBlock(self) if ADsettings.blockTracking : self.setValue(train.getName()) elif event.newValue == Block.UNOCCUPIED : # Train leaving the block # Check if the section is still occupied if ADpowerMonitor.powerOn : self.section.checkOccupancy() # Adjust track width in accordance to occupancy status self.adjustWidth() # TRACK ============== class ADtrack : # Encapsulates JMRI Track class, recording the original width def __init__(self, track): self.track = track # Save original track width self.width = track.getMainline() def setWidth(self, occupied): # Set width, depending on block's occupancy self.track.setMainline(occupied) def restoreWidth(self): # Restore original track width self.track.setMainline(self.width) # LOCATION ============== class ADlocation : # List of sections coresponding to each Operations' location # STATIC VARIABLES locationManager = LocationManager.instance() locations ={} # dictionary of location instances # STATIC METHODS def getList() : return ADlocation.locations.values() getList = ADstaticMethod(getList) def getNames() : return ADlocation.locations.keys() getNames = ADstaticMethod(getNames) def getByName(name) : return ADlocation.locations.get(name, None) getByName = ADstaticMethod(getByName) def getOpLocations() : # Get location IDs from Operations opIds = ADlocation.locationManager.getLocationsByNameList() for opId in opIds : # get Operation's location instance opLocation = ADlocation.locationManager.getLocationById(opId) name = opLocation.getName() # get our corresponding location or create it if ADlocation.locations.has_key(name) : location = ADlocation.locations[name] else : location = ADlocation(name) # Link our location with Operation's instance location.opLocation = opLocation getOpLocations = ADstaticMethod(getOpLocations) # INSTANCE METHODS def __init__(self, name): self.name = name ADlocation.locations[name] = self self.text = "" self.list = [] self.opLocation = None def setSections(self, text) : textSpace = text.replace(",", " ") tokens = textSpace.split() list = [] for t in tokens : s = ADsection.getByName(t) if s == None : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Location \"" + self.name + "\": wrong format or unknown section") return list.append(s) self.list = list self.text = text def getSections(self) : return self.list # DOUBLE CROSSOVER ============== class ADxover : # Keeps information about double crossovers (for single crossovers # there is no need, since they are simply two turnouts) def __init__(self, jmriXover): # Get the 4 layout blocks connected by the crossover block = [] self.section = [] block.append(jmriXover.getLayoutBlock()) block.append(jmriXover.getLayoutBlockB()) block.append(jmriXover.getLayoutBlockC()) block.append(jmriXover.getLayoutBlockD()) # Pick the corresponding ADblocks and ADsections for i in range(4) : # If block not specified, assume it to be block A if block[i] == None : block[i] = block[0] else : block[i] = ADblock.getByLayoutBlock(block[i]) if block[i] == None : # If one of the blocks is not included in any section # the Xover can be treated as a turnout and # this instance can be be purged return self.section.append(block[i].getSection()) # If any connected section is null (i.e. section not controlled # by AutoDispatcher), we can ignore the Xover (it will be treated # as a simple turnout) if self.section[i] == None : # (this instance will be purged) return # Check if the crossover is the intersection of two sections if (self.section[0] == self.section[2] and self.section[1] == self.section[3]) : # Intersection - is the crossover fully contained # in a single section? if self.section[0] == self.section[1] : # Yes - Ignore it, since there is little we can do! # (this instance will be purged) return # Intersection between two different sections # Treat it as a simple crossing self.section[0].addXing(self.section[1]) self.section[1].addXing(self.section[0]) # (this instance will be purged) return # Sections are not intersecting # Link the Xover with the relevant section entries self.__link__(self.section[0], block[0], self.section[2], block[2]) self.__link__(self.section[0], block[0], self.section[3], block[3]) self.__link__(self.section[1], block[1], self.section[2], block[2]) self.__link__(self.section[1], block[1], self.section[3], block[3]) self.__link__(self.section[2], block[2], self.section[0], block[0]) self.__link__(self.section[2], block[2], self.section[1], block[1]) self.__link__(self.section[3], block[3], self.section[0], block[0]) self.__link__(self.section[3], block[3], self.section[1], block[1]) def __link__(self, sectionFrom, blockFrom, sectionTo, blockTo): # Internal method - links the Xover to an entry point # Skip trivial case (both blocks in the same section) if sectionFrom == sectionTo : return # Find entry point for direction in [False, True] : for entry in sectionTo.getEntries(direction) : if (entry.getExternalSection() == sectionFrom and entry.getExternalBlock() == blockFrom and entry.getInternalBlock() == blockTo) : entry.addXover(self) return def isAvailable(self, comingFrom): # Checks if a route through the Xover can be used # Check only allocation of crossing routes # (other cases will anyway be solved when checking sections allocation) busyRoute = -1 for i in range(2) : train = self.section[i].getAllocated() if train != None : if self.section[i+2].getAllocated() == train : busyRoute = i break # If no route allocated, Xover is available if busyRoute < 0 : return True # Allocated route found # Xover not available, unless route allocated to the same train return (self.section[busyRoute] == comingFrom or self.section[busyRoute+2] == comingFrom) # GRID-LOCK PROTECTION GROUP ============== class ADgridGroup : # Each ADgridGroup instance contains the list of sections converging # into a transit-only section. These are point where gridlock may occur. # STATIC VARIABLES list = {} # STATIC METHODS def create() : # Create all gridLock groups ADgridGroup.list = {} # Scan all sections for section in ADsection.getList() : # Create groups for transit-only sections that accept # trains in both directions # (one-way sections do not create gridlock situations) if ((section.transitOnly[0] or section.transitOnly[1]) and section.direction == 3) : ADgridGroup(section) create = ADstaticMethod(create) def lockRisk(train, fromSection, toSection) : # Find if occupying a section can result in # a gridLock situation # Is the section included in a gridLock group group = ADgridGroup.list.get(fromSection.getName() + "$" + toSection.getName(), None) if group == None : # No return False # Yes - invoke the recursive inernal method ADgridGroup.debug = fromSection.getName() == "Lv1" return group.__lockRisk__(train, toSection, []) lockRisk = ADstaticMethod(lockRisk) # INSTANCE METHODS def __init__(self, transit) : # Keep note of the transit-only section, which is the # focus of the group self.transit = transit self.sections = [] self.bumpers = [] # Scan connected sections in both directions for direction in range(2) : dirSections = [] dirBumpers = [] for entry in transit.getEntries(not direction) : # Get the connected section section = entry.getExternalSection() dirSections.append(section) # Take note if the section is a siding dirBumpers.append(len(section.getEntries(direction)) == 0) # Create a dictionary entries for this section, # provided it's not one-way and gets accessed from a transit-only section # (focal section could not be transit-only in this direction) if self.transit.transitOnly[direction] and section.direction == 3 : # Get the list of sections from which this section can be entered for e in section.getEntries(not direction) : fromSection = e.getExternalSection() # Omit dictionary entries for one-way sections if (fromSection.direction & (direction + 1)) != 0 : # Create the entry, as follows: # startSection$endSection ADgridGroup.list[fromSection.getName() + "$" + section.getName()] = self self.sections.append(dirSections) self.bumpers.append(dirBumpers) def __lockRisk__(self, train, toSection, processed) : # Internal recursive method # Make sure section was not processed yet during this call # to avoid an endless loop! if toSection in processed : return False processed.append(toSection) # Compute direction with respect to sections storing order direction = toSection in self.sections[1] if not direction and not toSection in self.sections[0] : # If section is not included in this group (shouldn't happen) # ignore call return False # Examine sections parallel to the requested section # If one of them is free or occupied by a train running in opposite # direction, there is no gridLock risk for section in self.sections[direction] : if (section != toSection and (section.getAllocated() == None or section.trainDirection != direction)) : return False ind = 0 # Now look at sections on the other side of the transit only track for section in self.sections[not direction] : otherTrain = section.getAllocated() # Skip trivial case (oval with only one station) if train == otherTrain : return False # If section is free, or occupied by a train running in the same direction # of our train (and has no end-bumper), we need to make sure that we are not # creating a gridLock situation ahead if (otherTrain == None or (section.trainDirection == direction and not self.bumpers[not ind])) : next = ADgridGroup.list.get(self.transit.getName() + "$" + section.getName(), None) if next == None or next == self : return False if not next.__lockRisk__(train, section, processed) : return False ind += 1 return True # TRAIN ============== class ADtrain : # Our train class # CONSTANTS # Train status IDLE = 0 ERROR = 1 END_OF_SCHEDULE = 2 STOPPED = 3 ARRIVED = 4 STARTING = 5 STARTED = 6 # STATIC VARIABLES trains = [] # list of trains # Busy indicator (used to avoid that different threads may # operate turnouts at once) turnoutsBusy = False # List of sections used for Swing Interface sectionsList = None # STATIC METHODS def getList() : return ADtrain.trains getList = ADstaticMethod(getList) def remove(train) : ADtrain.trains.remove(train) remove = ADstaticMethod(remove) def buildRoster(trains) : # Build trains roster, based on user settings # (retrieved from disk or from defaults for example panels) for t in trains : tt = ADtrain(t[0]) tt.setDirection(t[2]) section = ADsection.getByName(t[1]) if section != None : tt.setSection(section, not ADsettings.autoRestart) tt.resistiveWheels = t[3] tt.setSchedule(t[4]) tt.trainAllocation = t[5] tt.trainLength = t[6] # Set schedule info if tt.section != None : tt.schedule.stack = t[7] tt.schedule.pointer = tt.schedule.stack[ len(tt.schedule.stack)-1] tt.schedule.pop() if len(t) > 12 : if t[12] != "" : tt.lastRouteSection = ADsection.getByName(t[12]) tt.changeLocomotive(t[8]) tt.setReversed(t[9]) # Rebuild pending commands, replacing names with instances for item in t[10] : section = ADsection.getByName(item[0]) if section != None : scheduleItem = ADscheduleItem() scheduleItem.action = item[1] scheduleItem.value = item[2] if len(item) > 3 : scheduleItem.message = item[3] if (scheduleItem.action == ADschedule.WAIT_FOR or scheduleItem.action == ADschedule.MANUAL_OTHER) : scheduleItem.value = ADsection.getByName( scheduleItem.value) if scheduleItem.value == None : continue elif (scheduleItem.action == ADschedule.HELD or scheduleItem.action == ADschedule.RELEASE or scheduleItem.action == ADschedule.IFH) : scheduleItem.value = ADsignalMast.getByName( scheduleItem.value) if scheduleItem.value == None : continue tt.itemSections.append(section) tt.items.append(scheduleItem) tt.engineerName = t[11] tt.trainSpeed = t[13] tt.brakingHistory = t[14] if len(t) > 15 : tt.canStopAtBeginning = t[15] if len(t) > 16 : tt.startAction = t[16] tt.updateSwing() buildRoster = ADstaticMethod(buildRoster) # INSTANCE METHODS def __init__(self, trainName) : # record this instance in the list of trains ADtrain.trains.append(self) # Initailize instance variables # Train name self.name = trainName # Corresponding train of Operations module (if any) self.opTrain = None # Locomotive name self.locoName = "" # Locomotive instance self.locomotive = None # Indicator of locomotive running backwards self.reversed = False # Indicator that train stops at start of sections that provide this option self.canStopAtBeginning = False # Indicator that train is presently running self.running = False # Train status self.status = ADtrain.IDLE # Present train speed level self.speedLevel = 0 # Speed conversion table self.trainSpeed = [] # Train departure time (-1L = immediate) self.departureTime = -1L # Departure time based on fast clock self.fastClock = False # Indicator that train is waiting for a section to become free # (None = no wait) self.waitFor = None # Indicator that train is operating in switching mode and can thus # enter any section self.switching = False # Protection flag. Suspends train processing while input data # are being updated self.updating = False # Protection flag. Suspends train processing while another thread # is already taking care of it self.checking = False # Direction of the train (referred to the internal conventional # direction) self.direction = 0 # Section where the head of the train is located self.section = None # Block where the head of the train is located self.block = None # Current destination section along current route self.destination = None # Direction at destination self.finalDirection = 0 # Exit signal of destination section self.destinationSignal = None # Last section of the route (can be different from present destination) self.lastRouteSection = None # Sections of current route to be still encountered self.sectionsAhead = [] # Blocks of current route to be still encountered self.blocksAhead = [] # Other sections still occupied by the train self.previousSections = [] # Sections ahead where commands need to be executed self.itemSections = [] # Commands to be executed (there is an item for each itemSection) self.items = [] # Train schedule self.schedule = ADschedule(" ") # Indicator that train is fully recovered in the current section self.safe = True # Indicator that train already reached the allocation point self.allocationReady = True # Number of sections along the route allocated self.allocatedSections = 0 # Indicator that cars are equipped with resistive wheels self.resistiveWheels = ADsettings.resistiveDefault # Number of sections ahead to be allocated for the train (0 = default) self.trainAllocation = 0 # Train length in mm. self.trainLength = 0.0 # Train length when leaving present section, provided by Operations module # (if used) self.opLength = 0.0 # Train distance from first section contained in previousSections # (used to release previous sections) self.distance = 0. # Length of current block self.blockLength = 0. # Last time train entered a block (used to detect stalled trains) self.lastMove = -1L # Maximum allowed train speed (may vary from block to block) self.maxSpeed = 0 # Actions to be played before train departure self.startAction = "" # Our engineer name self.engineerName = "Auto" # Our engineer class self.engineer = None # Available methods of the engineer # (Engineer classes may implement only some methods) self.engineerAssigned = False self.engineerSetTrain = None self.engineerSetLocomotive = None self.engineerSetLocoName = None self.engineerSetOrientation = None self.engineerSetFunction = None self.engineerChangeSpeed = None self.engineerPause = None self.engineerResume = None self.engineerRelease = None # Fields used for simulation mode # Current train route self.entriesAhead = [] # Sensor of previous block self.simSensor = None # Fields for self-learning braking # Previous block self.previousBlock = None # Collected braking data self.brakingHistory = {} # Swing fields, used in Trains window self.directionSwing = JComboBox([ADsettings.directionNames[0], ADsettings.directionNames[1]]) # Initialize sections' list if not yet done # The list will be used to populate combo boxes if ADtrain.sectionsList == None : ADtrain.sectionsList = [""] for section in ADsection.getList() : ADtrain.sectionsList.append(section.getName()) ADtrain.sectionsList.sort() self.sectionSwing = JComboBox(ADtrain.sectionsList) self.nameSwing = JTextField(self.name, 4) self.locoRoster = JComboBox() self.reversedSwing = JCheckBox("", self.reversed) self.reversedSwing.setHorizontalAlignment(JLabel.CENTER) self.scheduleSwing = JTextField("", 8) self.speedLevelSwing = AutoDispatcher.centerLabel("Idle") self.resistiveSwing = JCheckBox("", self.resistiveWheels) self.canStopAtBeginningSwing = JCheckBox("", self.canStopAtBeginning) self.trainLengthSwing = JTextField("0", 4) self.trainAllocationSwing = JComboBox(["Default", "1", "2", "3", "4", "5"]) self.trainAllocationSwing.setSelectedIndex(self.trainAllocation) self.engineerSwing = JComboBox() self.deleteButton = JButton("Delete") self.deleteButton.actionPerformed = self.whenDeleteTrainClicked self.changeButton = JButton("Apply") self.changeButton.actionPerformed = self.whenChangeClicked self.setButton = JButton("Apply") self.setButton.actionPerformed = self.whenSetClicked self.detailButton = JButton("Detail") self.destinationSwing = AutoDispatcher.centerLabel("None") self.startActionSwing = JTextField("", 5) self.enableSwing() def getName(self) : return self.name def getCanStopAtBeginning(self) : return self.canStopAtBeginning def getLength(self) : return self.trainLength def whenDeleteTrainClicked(self,event) : # Ask confirmation before deleting the train! if (JOptionPane.showConfirmDialog(None, "Remove train \"" + self.name + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) : return self.updating = True if self.running : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Train " + self.name + " running: cannot be deleted") self.updating = False return if self.opTrain != None and self.status == ADtrain.END_OF_SCHEDULE : try: self.opTrain.move() finally : i = 0 # Useless, just to complete the try construct AutoDispatcher.trainsFrame.deleteTrain(self) self.updating = True # define what Apply button in Trains Window does when clicked def whenChangeClicked(self,event) : self.trainChange() # define what Set button in Train Detail Window does when clicked def whenSetClicked(self,event) : self.scheduleSwing.text = AutoDispatcher.trainDetailFrame.scheduleSwing.text self.trainChange() def trainChange(self) : # Get input values from "Trains" and/or "Train Detail" windows # Check if this train is shown in the "Train Detail" window detail = (AutoDispatcher.trainDetailFrame != None and AutoDispatcher.trainDetailFrame.train == self) # Take note that instance is being updated, preventing other # threads from using it self.updating = True # Make sure another thread is not starting the train # (Let's cope with Jython's lack of synchronization) if self.running : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Train " + self.name + " running: changes cannot be applied") self.updating = False return # Change train's name newName = self.nameSwing.text if newName.strip() == "" : self.nameSwing.setText(oldName) elif newName != self.name : self.name = newName AutoDispatcher.setTrainsDirty() # Update train name in jmri Blocks (if needed) if self.section != None : if not self.section in self.previousSections : self.section.changeTrainName() for section in self.previousSections : section.changeTrainName() # Change train's direction oldDirection = self.direction self.setDirection(self.directionSwing.getSelectedItem()) # If direction changed, take note that we need to restart schedule reSchedule = oldDirection != self.direction # Change train's section sectionName = self.sectionSwing.getSelectedItem() if sectionName.strip() == "" : newSection = None else : newSection = ADsection.getByName(sectionName) if newSection != self.section : # Train was manually moved to another section # Take note that we need to restart schedule reSchedule = True self.setSection(newSection, True) self.schedule = None # Change train's locomotive loco = self.locoRoster.getSelectedItem() if loco != "" and loco != self.locoName : self.changeLocomotive(loco) AutoDispatcher.setTrainsDirty() # Change locomotive orientation newReversed = self.reversedSwing.isSelected() if self.reversed != newReversed : self.setReversed(newReversed) AutoDispatcher.setTrainsDirty() # Change train's schedule newSchedule = self.scheduleSwing.text if (self.schedule == None or self.schedule.pointer >= len(self.schedule.source) or self.schedule.text != newSchedule or reSchedule) : self.setSchedule(newSchedule) AutoDispatcher.setTrainsDirty() if detail : AutoDispatcher.trainDetailFrame.scheduleSwing.text = ( self.scheduleSwing.text) # Change train's resistive wheels indicator newResistiveWheels = self.resistiveSwing.isSelected() if self.resistiveWheels != newResistiveWheels : self.resistiveWheels = newResistiveWheels AutoDispatcher.setTrainsDirty() canStopAtBeginningSwing = self.canStopAtBeginningSwing.isSelected() if self.canStopAtBeginning != canStopAtBeginningSwing : self.canStopAtBeginning = canStopAtBeginningSwing AutoDispatcher.setTrainsDirty() # Change train length oldLength = self.trainLength try : self.trainLength = (float(self.trainLengthSwing.text) * ADsettings.units) except : self.trainLength = 0 self.trainLengthSwing.text = "0" if oldLength != self.trainLength : AutoDispatcher.setTrainsDirty() # Change number of sections ahead to be allocated for this train newAllocation = self.trainAllocationSwing.getSelectedIndex() if newAllocation != self.trainAllocation : self.trainAllocation = newAllocation AutoDispatcher.setTrainsDirty() # Change engineer newEngineerName = self.engineerSwing.getSelectedItem() if newEngineerName != self.engineerName : self.setEngineer(newEngineerName) AutoDispatcher.setTrainsDirty() # Change train speeds table ind = 0 for s in AutoDispatcher.trainDetailFrame.trainSpeedSwing : newSpeed = s.getSelectedIndex()+1 if newSpeed != self.trainSpeed[ind] : self.trainSpeed[ind] = newSpeed AutoDispatcher.setTrainsDirty() ind += 1 if self.startAction != self.startActionSwing.text : self.startAction = self.startActionSwing.text AutoDispatcher.setTrainsDirty() # Update swing fields self.updateSwing() if AutoDispatcher.trainsFrame != None : AutoDispatcher.trainsFrame.reDisplay() # Updating complete, let other threads use this instance self.updating = False if detail : AutoDispatcher.trainDetailFrame.dispose() AutoDispatcher.trainDetailFrame = None AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Changes for train \"" + self.name + "\" applied") def setDirection(self, direction) : # Set train direction if (direction == "CCW" or direction == "EAST" or direction == "NORTH" or direction == "LEFT" or direction == "UP") : newDirection = ADsettings.ccw else : newDirection = 1 - ADsettings.ccw if newDirection == self.direction : return self.direction = newDirection if self.section != None : self.section.trainDirection = self.direction def getDirection(self) : return self.direction def setSection(self, newSection, held) : # Set initial position of train if newSection != self.section : # Train placed in a new section if self.section != None : # Remove train from the old sections self.section.trainHead = False self.releaseSections() # self.section.allocate(None, 0) self.section = None self.safe = False self.simSensor = None if newSection != None : # Check that section is not allocated to another train otherTrain = newSection.getAllocated() if otherTrain != None and otherTrain != self : AutoDispatcher.message("Section " + newSection.getName() + " already allocated to train " + otherTrain.getName()) newSection = None else : # Check that section is actually occupied newSection.occupied = True newSection.checkOccupancy() if not newSection.isOccupied() : # Train is being placed in an empty section # Are we running in simulation mode? if AutoDispatcher.simulation : # Simulation mode - Force section occupancy # Find out block to be used sensor = newSection.stopBlock[ self.direction].getOccupancySensor() if sensor != None : # Avoid "wrong route" error wrongRoute = ADsettings.wrongRouteDetection ADsettings.wrongRouteDetection = ( ADsettings.DETECTION_DISABLED) newSection.occupied = True newSection.empty = False # Force occupancy sensor.setKnownState(Sensor.ACTIVE) ADsettings.wrongRouteDetection = wrongRoute else : # Block has no sensor! :-O newSection = None else : # We are not running in simulation mode. # User should place train on tracks before # defining it (unless derailment detection is disabled)! if (ADsettings.derailDetection != ADsettings.DETECTION_DISABLED) : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Section " + newSection.getName() + " is empty. Place train " + self.name + " on tracks!") # Attempt to assign train to an empty section, if (ADsettings.derailDetection == ADsettings.DETECTION_PAUSE) : # set section to undefined newSection = None # Was assignment successful? if newSection != None : self.section = newSection # Yes - Update allocation self.section.allocate(self, self.direction) self.destination = self.lastRouteSection = self.section self.finalDirection = self.direction self.destinationSignal = self.section.getSignal( self.direction) self.block = self.section.stopBlock[self.direction] self.previousBlock = None # Clear distance from previous sections self.distance = 0. self.blocklength = 0. # Set signal in front of train to held # (only if the signal icon is displayed on the panel, # otherwise user will not be able to release it! s = self.section.getSignal(self.direction) if held and s.hasIcon() : s.setHeld(True) else : # Unsuccessful assignment - clear section name self.sectionSwing.setSelectedItem("") def releaseSections(self) : # Routine to release sections occupied by the train, # when manually moving it to another section or deleting it for section in ADsection.getList() : if section.getAllocated() == self : section.allocate(None, 0) if AutoDispatcher.simulation : section.occupied = False section.empty = True for block in section.getBlocks(True) : sensor = block.getOccupancySensor() if sensor != None : sensor.setKnownState(Sensor.INACTIVE) block.adjustWidth() else : section.checkOccupancy() section.setColor() AutoDispatcher.repaint = True def changeSection(self, newSection) : # Change section containing train's head while train is running # Knowing where the head of the train is located is # necessary for proper action of signals and # to detect derailments (or other malfunctions) # If a section containing the head of the train becomes empty, # something went wrong! # Avoid re-triggering a previous section if newSection in self.previousSections or newSection == self.section : return # Train moved to a new section if self.section != None : # Remove the head of the train from the old section (if any) self.section.trainHead = False # Reset signals if self.section.signal[0] != None : self.section.signal[0].setIndication(0) if self.section.signal[1] != None : self.section.signal[1].setIndication(0) # Since train is just entering a new section it cannot be safely # recovered in it self.safe = False self.section = newSection if self.section != None : # Must previous sections be still released? if len(self.previousSections) == 0 : # No, clear distance from previous sections self.distance = 0. self.blocklength = 0. # Is train moving to a section along the route? (it should) if self.section in self.sectionsAhead : # Yes # Remove it and all previous sections from the route, # adjusting train direction (if changed) oldDirection = self.direction while self.section in self.sectionsAhead : # Adjust direction passedSection = self.sectionsAhead.pop(0) self.direction = passedSection.trainDirection # Mark section as occupied passedSection.setOccupied() passedSection.checkOccupancy() # Update number of allocated section if (self.allocatedSections > 0 and (not passedSection.transitOnly[self.direction] or passedSection.getSignal(self.direction).hasHead())) : self.allocatedSections -= 1 # Keep note that this section was passed and must still # be released self.previousSections.append(passedSection) self.section.trainHead = True # Update Trains window if direction changed if self.direction != oldDirection : self.updateSwing() # Update sections color if direction changed for section in self.previousSections : section.setColor() for section in self.sectionsAhead : section.setColor() # Update Trains window (if open) self.sectionSwing.setSelectedItem(self.section.getName()) # Move ahead train position in Operations module (if any) if self.opTrain != None : if self.opLength > 0. : self.trainLength = self.opLength self.opLength = 0. try : nextLocation = ADlocation.getByName( self.opTrain.getNextLocationName()) if nextLocation != None : if self.section in nextLocation.getSections() : self.opTrain.move() opCurrent = self.opTrain.getCurrentLocation() if opCurrent != None : self.opLength = round(float( opCurrent.getTrainLength( )) * 304.8 / ADsettings.scale) finally : i = 0 else : # Train ended nowhere :-O self.sectionSwing.setSelectedItem("") # Should train stop at the start of this section? if self.shouldStopAtBeginning() : if self.locomotive.learningBrake() : if self.speedLevel > self.maxSpeed : self.changeSpeed(self.maxSpeed) else : # Yes, brake self.changeSpeed(1) def shouldStopAtBeginning(self) : if not self.canStopAtBeginning : return False if self.section != self.destination : return False if self.section.stopAtBeginning[self.direction] < 0 : return False return True def changeBlock(self, newBlock) : # Check if we need to compute distance from previous sections if len(self.previousSections) == 0 : firstPassedSection = self.section else : firstPassedSection = self.previousSections[0] speed = self.speedLevel # Remove block from list of expected blocks in order to # avoid re-triggering. Also blocks that were skipped # i.e. did not trigger any event (malfunctioning sensor?) # are processed while newBlock in self.blocksAhead : self.previousBlock = self.block self.block = self.blocksAhead.pop(0) self.lastMove = System.currentTimeMillis() section = self.block.getSection() # Update maximum speed newSpeed = self.block.getSpeed(self.direction) if newSpeed > 0 : self.maxSpeed = newSpeed # Apply new speed if train is not braking or stopping if speed > 1 : speed = self.maxSpeed self.changeSection(section) # Update distance from previous sections (unless we are still # in the first section of the route) if firstPassedSection != section : self.distance += self.blockLength self.blockLength = self.block.getLength() section.sectionLength += self.blockLength # Update locomotive mileage if self.locomotive != None : # We actually do it in advance, but for our purposes it's fine self.locomotive.mileage += self.block.getLength() # allocation point? if section.allocationPoint[self.direction] == self.block : self.allocationReady = True if section == self.section : # safe point? if self.section.safePoint[self.direction] == self.block : # Take note that the train is safely contained within # section boundaries self.safe = True # Is train still starting ? if self.status != ADtrain.STARTING : # No, get speed indicated by signal restrictedSpeed = self.section.getSignal( self.direction).getSpeed() # Should train stop at start of section ? shouldStop = self.shouldStopAtBeginning() # stop block? if self.section.stopBlock[self.direction] == self.block : # Apply restricted speed # if restrictedSpeed > 0 : speed = restrictedSpeed if speed == 0 : if not shouldStop : self.arrived() # If we have a locomotive, inform it that train # reached stop block (in case it's implementing # self-learning braking) if self.locomotive != None : self.locomotive.learningStop() # If we don't have a locomotive or are running in # simulation, assume that train stops if (self.locomotive == None or self.engineerSetLocomotive == None or AutoDispatcher.simulation) : self.stop() elif shouldStop : speed = self.speedLevel # Brake block? elif self.section.brakeBlock[self.direction] == self.block : # Set speed to minimum if exit signal is red if restrictedSpeed == 0 : # If we have a locomotive, inform it that train # starts braking (in case it's implementing # self-learning braking) if self.locomotive != None : if not self.locomotive.learningBrake() : # self-learning not active # Brake immediately speed = 1 else : # Self-learning braking not implemented # Brake immediately speed = 1 # If signal is not red, set speed to value indicated # by signal (if lower than present speed) else : speed = restrictedSpeed # Almost done. # Call change speed even if speed did not change, in order to inform # the Engineer that some change occurred self.changeSpeed(speed) # Start a separate thread to take care of block actions (if any) if self.block.action[self.direction].strip() != "" : start_new_thread(self.doAction, (self.block.action[self.direction],)) # Release previously occupied sections (if empty) self.section.freeSectionIfTrainSafe() def arrived(self) : # Assume train is stopping self.lastMove = -1L if AutoDispatcher.runningTrains > 0 : AutoDispatcher.runningTrains -= 1 if self.section.isManual() : self.section.setColor() def stop(self) : # Train stops self.running = False self.enableSwing() def doAction(self, actionList) : # Run in a separate thread to perform block actions textSpace = actionList.replace(",", " ") # Break down input text into tokens splitted = textSpace.split() for sl in splitted : if sl.startswith("$") and len(sl) > 1 : sl = sl[1:] action = ADschedule.ERROR s = sl.upper() # DCC function ON/OFF if s.startswith("ON:F") : action = ADschedule.SET_F_ON value = s[4:] elif s.startswith("OFF:F") : action = ADschedule.SET_F_OFF value = s[5:] if action != ADschedule.ERROR : # Retrieve ON OFF argument (function number) try : value = int(value) if value < 0 or value > 28 : continue except : continue if action == ADschedule.SET_F_ON : # Set decoder function ON self.setFunction(value, True) else : # Set decoder function OFF self.setFunction(value, False) # Delay n seconds or m fastclock minutes elif s.startswith("D") : try : s = s[1:] except : continue if s.startswith("M") : try : s = s[1:] except : continue useFastClock = True else : useFastClock = False try : value = float(s) except : continue if useFastClock : value = (value * 60. / AutoDispatcher.fastBase.getRate()) sleep(value) # Play AudioClip elif s.startswith("S:") : try : value = ADsettings.soundDic.get(sl[2:], None) if value != None : value.play() except : continue # Set turnout elif s.startswith("TC:") or s.startswith("TT:") : try : value = sl[3:] t = InstanceManager.turnoutManagerInstance().getTurnout(value) if s.startswith("TC:") : t.setState(Turnout.CLOSED) AutoDispatcher.message("Closed turnout " + value) else : t.setState(Turnout.THROWN) AutoDispatcher.message("Thrown turnout " + value) except : continue def setSchedule(self, schedule) : # Set train schedule self.schedule = ADschedule(schedule) self.status = ADtrain.IDLE self.speedLevelSwing.text == "Idle" self.waitFor = None self.departureTime = -1L self.destination = lastRouteSection = self.section self.finalDirection = self.direction self.sectionsAhead = [] self.entriesAhead = [] self.blocksAhead = [] self.itemSections = [] self.items = [] # Update sections' colors and status (releasing possible sections # allocated to the train) for section in ADsection.getList() : if section.getAllocated() == self and not section.isOccupied() : section.allocate(None, 0) else : section.setColor() if self.section != None : # Set destination signal to exit signal of current section self.destinationSignal = self.section.signal[self.direction] # Try to find current section in new schedule self.schedule.match(self.section, self.direction) self.queueCommands() # Force panel repainting AutoDispatcher.repaint = True def changeLocomotive(self, locoName) : # Set/replace locomotive # If same locomotive as before ignore call if self.locoName == locoName : return self.locoName = locoName # Release previous locomotive locomotive = self.locomotive if locomotive != None : self.locomotive = None locomotive.usedBy = None locomotive.releaseThrottle() # If no new locomotive was selected, return if self.locoName.strip() == "" : return # Retrieve locomotive instance self.locomotive = ADlocomotive.getByName(self.locoName) if self.locomotive == None : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Locomotive " + locoName + " not found!") else : # Locomotive found self.locomotive.usedBy = self # Force assignment of engineer self.engineerAssigned = False self.status = ADtrain.IDLE def setReversed(self, reversed) : # Set locomotive orientation self.reversed = reversed def clearBrakingHistory(self, locomotive) : if locomotive == None : # No locomotive specified. Clear history of all locomotives self.brakingHistory = {} else : # Rebuild history dictionary skipping entries # for specified locomotive keyStart = locomotive.getName() + "$" newHistory = {} for key in self.brakingHistory.keys() : if not key.startswith(keyStart) : newHistory[key] = self.brakingHistory[key] self.brakingHistory = newHistory def setEngineer(self, engineerName) : self.engineerName = engineerName self.engineerAssigned = False def assignEngineer(self) : # If engineer already assigned, ignore call if self.engineerAssigned : return # Release previous Engineer, if any self.releaseEngineer() self.engineerAssigned = True # If user chose manual control, no other action is needed if self.engineerName == "Manual" : return # Retrieve engineer class if not AutoDispatcher.engineers.has_key(self.engineerName) : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Engineer script " + self.engineerName + " not found!") self.engineerName = "Auto" # Create engineer instance self.engineer = AutoDispatcher.engineers[self.engineerName]() # Check what methods are implemented by this engineer class self.engineerSetTrain = self.getEngineerMethod("setTrain") self.engineerSetLocomotive = self.getEngineerMethod("setLocomotive") self.engineerSetLocoName = self.getEngineerMethod("engineerSetLocoName") self.engineerSetOrientation = self.getEngineerMethod("setOrientation") self.engineerSetFunction = self.getEngineerMethod("setFunction") self.engineerChangeSpeed = self.getEngineerMethod("changeSpeed") self.engineerPause = self.getEngineerMethod("pause") self.engineerResume = self.getEngineerMethod("resume") self.engineerRelease = self.getEngineerMethod("release") # Inform engineer that he is assigned to this train :-) self.callEngineer(self.engineerSetTrain, self) def getEngineerMethod (self, methodName) : # Check if a method exists method = getattr(self.engineer, methodName, None) if not callable(method) : return None return method def setOrientation(self, forward) : # Set locomotive orientation # Does the engineer provide the relevant method? if self.engineerSetOrientation == None : # No, if we have a locomotive pass the call directly # to the locomotive locomotive = self.locomotive if locomotive != None : locomotive.setOrientation(forward) else : # Engineer provides the appropriate method, invoke it self.callEngineer(self.engineerSetOrientation, forward) def setFunction(self, functionNumber, on) : # Set/reset a locomotive function # Does the engineer provide the relevant method? if self.engineerSetFunction == None : # No, if we have a locomotive pass the call directly # to the locomotive locomotive = self.locomotive if locomotive != None : locomotive.setFunction(functionNumber, on) else : # Engineer provides the appropriate method, invoke it self.callEngineer(self.engineerSetFunction, functionNumber, on) def changeSpeed(self, suggestedSpeed) : # Change train speed # Make sure that speed does not exceed block's maximum speed if suggestedSpeed > self.maxSpeed and self.maxSpeed != 0 : suggestedSpeed = self.maxSpeed # If speed changed, update Trains window (if open) if self.speedLevel != suggestedSpeed : self.outputSpeed(suggestedSpeed) self.speedLevel = suggestedSpeed # Convert speed level, using train's correspendence table suggestedSpeed = self.convertSpeed(suggestedSpeed) # Does the engineer provide the relevant method? if self.engineerChangeSpeed == None : # No, if we have a locomotive pass the call directly # to the locomotive locomotive = self.locomotive if locomotive != None : locomotive.changeSpeed(suggestedSpeed) else : # Engineer provides the appropriate method, invoke it # Engineer is called even if speed value did not change, in order to # inform about other changes (e.g. section or block) # The Engineer class will decide if any action is required max = self.maxSpeed if max == 0 : max = len(ADsettings.speedsList) max = self.convertSpeed(max) self.callEngineer(self.engineerChangeSpeed, self.section, self.block, self.section.getSignal(self.direction), suggestedSpeed, max) # If user chose to switch off lights when train stops, do it if self.speedLevel == 0 and ADsettings.lightMode == 1 : self.setFunction(0, False) def outputSpeed(self, speed) : # Output speed level to Trains window if speed == 0 : if self.departureTime > -1L or self.waitFor != None : return self.speedLevelSwing.text = "Stop" else : self.speedLevelSwing.text = ADsettings.speedsList[speed-1] def convertSpeed(self, speed) : # Convert speed level, using train's correspondence table # (User can decide that this train should run at 10mph when # the prescribed speed is 20mph) if speed > 0 and len(self.trainSpeed) >= speed : speed = self.trainSpeed[speed-1] if speed > len(ADsettings.speedsList) : speed = len(ADsettings.speedsList) return speed def pause(self) : # Script is pausing, train must be halted # Does the engineer provide the relevant method? if self.engineerPause == None : # No, if we have a locomotive pass the call directly # to the locomotive locomotive = self.locomotive if locomotive != None : locomotive.pause() else : # Engineer provides the appropriate method, invoke it self.callEngineer(self.engineerPause) # Take note that train stopped, to avoid "Stalled train" error self.lastMove = -1L def resume(self) : # Script is resuming after a pause, train must be restarted # Set correct locomotive direction, in case it was manually changed # during the pause self.setOrientation(not self.reversed) # Does the engineer provide the relevant method? if self.engineerResume == None : # No, if we have a locomotive pass the call directly # to the locomotive locomotive = self.locomotive if locomotive != None : locomotive.resume() else : # Engineer provides the appropriate method, invoke it self.callEngineer(self.engineerResume) if self.running : self.lastMove = System.currentTimeMillis() def releaseEngineer(self) : # Release the present engineer # Does the engineer provide a release method? if self.engineer != None and self.engineerRelease != None : # Yes, invoke it self.callEngineer(self.engineerRelease) # Clear all engineer related variables self.engineer = None self.engineerSetTrain = None self.engineerSetLocomotive = None self.engineerSetLocoName = None self.engineerSetOrientation = None self.engineerSetFunction = None self.engineerChangeSpeed = None self.engineerPause = None self.engineerResume = None self.engineerRelease = None self.engineerAssigned = False def callEngineer(self, method, *args) : # Invoke a method of the engineer class # Number of arguments may vary if method != None : start_new_thread(method, args) def startIfReady(self) : # Start the train, if it can reach a section towards its destination # If train reached the end of the schedule or is in error simply exit # Avoid any action if train is being updated or started by # another thread if (self.updating or self.checking or self.status == ADtrain.ERROR or self.status == ADtrain.END_OF_SCHEDULE) : return # If the train is nowhere, exit! if self.section == None : return loco = self.locomotive # Is train in a manually controlled section? if self.section.isManual() : # Manual section, release throttle if train stopped if (not self.running and self.locomotive != None and self.locomotive.throttle != None and self.locomotive.getThrottleSpeed() <= 0) : self.locomotive.releaseThrottle() else: # Not manual section - Assign engineer, if not yet done if not self.engineerAssigned : self.assignEngineer() if loco != None : if self.engineerSetLocomotive != None : self.callEngineer(self.engineerSetLocomotive, loco) else : self.callEngineer(self.engineerSetLocoName, loco.getName()) # Does train have a locomotive? if loco != None : # Assign throttle, if not yet done # (only if engineer implements setLocomotive method, otherwise # having a locomotive is useless) if (not loco.throttleAssigned and self.engineerSetLocomotive != None) : loco.assignThrottle() return # If train is paused, check if time has expired if self.departureTime > -1L : if self.fastClock : if self.departureTime > FastListener.fastTime : return elif self.departureTime > System.currentTimeMillis() : return self.departureTime = -1L self.outputSpeed(self.speedLevel) # If train is waiting for a section, see if the section is available # (or already occupied by the train) if self.waitFor != None : if (self.waitFor.isAvailable() or (self.waitFor == self.section and not self.section.isManual())) : self.waitFor = None if self.speedLevelSwing.text.startswith("WAITING") : self.outputSpeed(self.speedLevel) else : return # Process commands prefixed by $ that can be executed without # stopping the train # Such commands were transferred to "items" array before train departure if self.section in self.itemSections : self.itemSections.pop(0) self.processCommands(self.items.pop(0)) return # If running, see if destination was reached if ((self.status == ADtrain.STARTED or self.status == ADtrain.IDLE) and self.section == self.destination) : self.status = ADtrain.ARRIVED # Process commands prefixed by $ that can be executed only when # train is not running # (such commands are still contained in the schedule) scheduleItem = self.schedule.getFirstAlternative() if not self.running and self.status == ADtrain.ARRIVED : if scheduleItem.action != ADschedule.GOTO : # Take care of possible $IF commands scheduleItem = self.schedule.testCondition(scheduleItem, self.destination, self.direction) # Now execute this command self.processCommands(scheduleItem) # and pick up the next one self.schedule.next() return self.status = ADtrain.STOPPED # Whether running or not, GOTO command can be implemented without # waiting for train to stop if (scheduleItem.action != ADschedule.GOTO or self.destination.isManual()) : return # If signal held, exit if self.destinationSignal.isHeld() : if not self.running and self.destinationSwing.text != "Held": self.destinationSwing.text = "Held" return # Is train eligible for starting? if self.trainAllocation == 0 : allocationAhead = ADsettings.allocationAhead else : allocationAhead = self.trainAllocation if (not self.allocationReady or self.allocatedSections >= allocationAhead) : return # Limit number of running trains if (not self.running and (ADsettings.max_trains > 0 and AutoDispatcher.runningTrains >= ADsettings.max_trains)) : if (not self.running and self.destinationSwing.text != "Waiting turn"): self.destinationSwing.text = "Waiting turn" return # Make sure anther thread is not starting this train or # operating turnouts if ADtrain.turnoutsBusy or self.checking : return ADtrain.turnoutsBusy = self.checking = True # Span a separate thread in order to try and start the train # In this way commands for other trains can be processed while # starting this train (and operating turnouts) start_new_thread(self.checkAndStart, ()) def checkAndStart(self) : destinationText = "" # Compute number of sections ahead to be allocated if self.trainAllocation == 0 : allocationAhead = ADsettings.allocationAhead else : allocationAhead = self.trainAllocation if (self.lastRouteSection != None and self.destination != self.lastRouteSection) : # Train running in burst mode - try reaching final section # of previous route route = ADautoRoute(self.destination, self.lastRouteSection, self.finalDirection, self.switching) route.reduce(self, allocationAhead) foundLength = len(route.step) atLeastOne = True else : # Final section of previous schedule step reached # Find a possible route to one of alternate destinations included # in the new schedule step route = None atLeastOne = False foundLength = 0 scheduleItem = self.schedule.getFirstAlternative() while scheduleItem.action == ADschedule.GOTO : if destinationText != "" : if not destinationText.startswith("[") : destinationText = "[" + destinationText destinationText += " " destinationText += scheduleItem.value.getName() newRoute = ADautoRoute(self.destination, scheduleItem.value, self.finalDirection, self.switching) # Any route to this section found? if len(newRoute.step) > 0 : # Yes atLeastOne = True # See if we can advance along it. newRoute.reduce(self, allocationAhead - self.allocatedSections) newLength = len(newRoute.step) # Is this the longest reduced route? if newLength > foundLength : # Yes, take note foundLength = newLength route = newRoute scheduleItem = self.schedule.getNextAlternative() if scheduleItem.action == ADschedule.ERROR : # Wrong schedule if not self.running : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Error in schedule of train \"" + self.name + "\": " + scheduleItem.message) self.status = ADtrain.ERROR self.destinationSwing.text = "ERROR" self.checking = ADtrain.turnoutsBusy = False return if destinationText.startswith("[") : destinationText += "]" # Check if any error was encounterd (i.e. transit-only destination) if route != None and route.error != None : # Error - Schedule contains transit-only destination if not self.running : # Output message only when train stops, otherwise we will # flood user with messages AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Schedule error: Train " + self.name + " headed to transit-only section " + route.error.getName()) self.status = ADtrain.ERROR destinationText = "ERROR" foundLength = 0 atLeastOne = True if foundLength == 0 : # A reduced route was not found, check if at least one valid # route was found if not atLeastOne : # No valid route was found, clearly an error # in schedule definition if not self.running : # Output message only when train stops, otherwise we will # flood user with messages AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Train " + self.name + ": no valid route from section " + self.section.getName()) self.status = ADtrain.ERROR destinationText = "ERROR" # Even if a valid route was found, next section is occupied or # allocated: exit if destinationText != "" : self.destinationSwing.text = destinationText self.checking = ADtrain.turnoutsBusy = False return # Minimum route found, try allocating sections newAllocatedSections = route.allocate(self) if len(route.step) == 0 : # First section along the route is occupied by another train or # new route is no longer valid: exit if destinationText != "" : self.destinationSwing.text = destinationText self.checking = ADtrain.turnoutsBusy = False return # Successful allocation, train can start (or continue running) self.allocatedSections += newAllocatedSections self.destinationSwing.text = self.lastRouteSection.getName() # If present block has no maximum speed, use default speed startSpeed = self.maxSpeed if startSpeed == 0 : startSpeed = len(ADsettings.speedsList) self.status = ADtrain.STARTING # Increase count of running trains if not self.running : self.running = True AutoDispatcher.runningTrains += 1 # Disable input in Trains window self.enableSwing() # Make sure user is not updating train data # (Let's cope with Jython's lack of synchronization) while self.updating : AutoDispatcher.instance.waitMsec(100) if AutoDispatcher.simulation : self.entriesAhead.extend(route.step) # Create a list of sections that the train will use # (the list will be employed to track train movement) if not self.section in self.previousSections : self.previousSections.append(self.section) # Force panel re-drawing AutoDispatcher.repaint = True # Set turnouts route.setTurnouts() # Update list of blocks ahead for block in route.blocksList : if not block in self.blocksAhead : self.blocksAhead.append(block) # Train must reach next allocation point before being re-considered # for scheduling, unless allocated sections was lower than maximum self.allocationReady = self.allocatedSections < allocationAhead # Wait if user specified a delay before clearing signals if ADsettings.clearDelay > 0 : AutoDispatcher.instance.waitMsec(ADsettings.clearDelay) # Set exit signals route.clearSignals(self) # Is our current destination the target of the present schedule item? scheduleItem = self.schedule.getFirstAlternative() while scheduleItem.action == ADschedule.GOTO : if self.destination == scheduleItem.value : # Yes - Take note of commands to be executed while running self.schedule.next() self.queueCommands() break scheduleItem = self.schedule.getNextAlternative() # Since train is (about) moving, user must be given the posibility # of saving its new position to disk before quitting the program. AutoDispatcher.setTrainsDirty() # Is train starting from still (or was it already running)? if self.speedLevel <= 0 : # Starting from still. Set locomotive direction (could have changed) self.setOrientation(not self.reversed) # Delay departure, if user chose this option if (ADsettings.startDelayMin > 0 or ADsettings.startDelayMax > 0) : delay = ADsettings.startDelayMin + int( float(ADsettings.startDelayMax - ADsettings.startDelayMin) * AutoDispatcher.random.nextDouble()) AutoDispatcher.instance.waitMsec(delay) # Switch front light on, if user chose this option if ADsettings.lightMode != 0 : self.setFunction(0, True) # Play start actions, if any if self.startAction != "" : self.doAction(self.startAction) elif ADsettings.defaultStartAction != "" : self.doAction(ADsettings.defaultStartAction) # Now start train! if (self.section.stopBlock[self.direction] == self.block or self.section.brakeBlock[self.direction] == self.block) : restrictedSpeed = self.section.getSignal(self.direction).getSpeed() if restrictedSpeed <= self.maxSpeed or self.maxSpeed == 0 : startSpeed = restrictedSpeed if self.locomotive != None : self.locomotive.brakeAdjusting = False self.changeSpeed(startSpeed) # Take note that train was started self.lastMove = System.currentTimeMillis() self.status = ADtrain.STARTED self.checking = ADtrain.turnoutsBusy = False return def queueCommands(self) : # Transfer commands from schedule to pending comands queue (items) # Unless they were already transferrred (in the later case, # commands are discarded) toBeAdded = not self.destination in self.itemSections scheduleItem = self.schedule.getFirstAlternative() # Transfer only commands that can be executed while train is running while scheduleItem.action > ADschedule.GOTO : if toBeAdded : self.itemSections.append(self.destination) self.items.append(scheduleItem) self.schedule.next() scheduleItem = self.schedule.getFirstAlternative() def processCommands(self, scheduleItem) : # Process schedule commands prefixed by $ if scheduleItem.action == ADschedule.SWON : # Set train into "switching mode" self.switching = True AutoDispatcher.message("Train " + self.name + " entering switching mode") return if scheduleItem.action == ADschedule.SWOFF : # Clear "switching mode" self.switching = False AutoDispatcher.message("Train " + self.name + " exiting switching mode") return if scheduleItem.action == ADschedule.HELD : # Set signal to "held" state scheduleItem.value.setHeld(True) AutoDispatcher.message("Signal " + scheduleItem.value.getName() + " held") return if scheduleItem.action == ADschedule.RELEASE : # Clear "held" state of signal scheduleItem.value.setHeld(False) AutoDispatcher.message("Signal " + scheduleItem.value.getName() + " released") return if scheduleItem.action == ADschedule.SET_F_ON : # Set decoder function ON self.setFunction(scheduleItem.value, True) return if scheduleItem.action == ADschedule.SET_F_OFF : # Set decoder function OFF self.setFunction(scheduleItem.value, False) return if scheduleItem.action == ADschedule.WAIT_FOR : # Wait for section empty self.waitFor = scheduleItem.value AutoDispatcher.message("Train " + self.name + " waiting for section" + self.waitFor.getName()) self.destinationSwing.setText("$WF:"+self.waitFor.getName()) return if scheduleItem.action == ADschedule.DELAY : # Delay next commands delay = int(scheduleItem.value * 1000.) if delay > 0 : self.fastClock = False self.departureTime = (System.currentTimeMillis() + delay) return if scheduleItem.action == ADschedule.MANUAL_PRESENT : self.switchToManual(self.section) return if scheduleItem.action == ADschedule.MANUAL_OTHER : self.switchToManual(scheduleItem.value) return if scheduleItem.action == ADschedule.ERROR : # Wrong schedule AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Error in schedule of train \"" + self.name + "\": " + scheduleItem.message) self.status = ADtrain.ERROR return if scheduleItem.action == ADschedule.STOP : # End of schedule reached self.status = ADtrain.END_OF_SCHEDULE if ADsettings.lightMode == 2 : self.setFunction(0, False) AutoDispatcher.message("End of schedule for train " + self.name) self.destinationSwing.setText("End") if self.opTrain != None : try: self.opTrain.move() finally : i = 0 # Useless, just to complete the try construct return # Test for change of direction command newDirection = -1 if scheduleItem.action == ADschedule.CCW : newDirection = ADsettings.ccw elif scheduleItem.action == ADschedule.CW : newDirection = 1-ADsettings.ccw if newDirection > -1 : # Change of direction if newDirection != self.direction : # Allow enough time for simulation step to complete if AutoDispatcher.simulation : AutoDispatcher.instance.waitMsec(1000) self.direction = self.finalDirection = newDirection # We are now facing the opposite signal self.destinationSignal = self.section.signal[self.direction] # Reverse locomotive direction self.reversed = not self.reversed # Change direction in section self.section.allocate(self, self.direction) # Change color of all sections occupied by the train if not self.section in self.previousSections : self.section.setColor() for section in self.previousSections : section.setColor() for section in self.sectionsAhead : section.setColor() AutoDispatcher.repaint = True AutoDispatcher.message("Train " + self.name + " headed " + ADsettings.directionNames[1-newDirection]) self.updateSwing() return if scheduleItem.action == ADschedule.PAUSE : AutoDispatcher.message("Train " + self.name + " pausing for " + str(scheduleItem.value) + " sec.") delay = int(scheduleItem.value * 1000.) if delay > 0 : # Pause - Compute departure time self.fastClock = False self.departureTime = (System.currentTimeMillis() + delay) self.destinationSwing.setText("$P"+str(scheduleItem.value)) self.updateSwing() return if scheduleItem.action == ADschedule.START_AT : if scheduleItem.value > FastListener.fastTime : self.fastClock = True self.departureTime = scheduleItem.value hours = int(scheduleItem.value/60) minutes = scheduleItem.value - hours * 60 if minutes > 9 : hours = str(hours) + ":" + str(minutes) else : hours = str(hours) + ":0" + str(minutes) AutoDispatcher.message("Train " + self.name + " waiting until " + hours) self.destinationSwing.setText("$ST "+ hours) self.updateSwing() return if scheduleItem.action == ADschedule.SOUND : # Play sound scheduleItem.value.play() return if (scheduleItem.action == ADschedule.TC or scheduleItem.action == ADschedule.TT) : # Set turnout (or other accessory) try : t = InstanceManager.turnoutManagerInstance().getTurnout(scheduleItem.value) except : t = None if t == None : AutoDispatcher.log("Error in schedule of train \"" + self.name + "\":") AutoDispatcher.chimeLog(" Unknown turnout \"" + scheduleItem.value + "\"") self.status = ADtrain.ERROR return if scheduleItem.action == ADschedule.TC : t.setState(Turnout.CLOSED) AutoDispatcher.message("Train " + self.name + " : closed turnout " + scheduleItem.value) else : t.setState(Turnout.THROWN) AutoDispatcher.message("Train " + self.name + " : thrown turnout " + scheduleItem.value) return def switchToManual(self, section) : # Try switching the section to Manual control section.setManual(True) # Check result if section.isManual() : # Fine AutoDispatcher.message("Section \"" + section.getName() + "\" switched to manual control") else : # Failed. Section probably does not have a "Manual control" sensor AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Cannot switch section \"" + section.getName() + "\" to manual control") def updateSwing(self) : # Update SWING I/O fields for train self.directionSwing.removeAllItems() self.directionSwing.addItem(ADsettings.directionNames[0]) self.directionSwing.addItem(ADsettings.directionNames[1]) if ADsettings.ccw == 0 : self.directionSwing.setSelectedIndex(self.direction) else : self.directionSwing.setSelectedIndex(1- self.direction) if self.section != None : self.sectionSwing.setSelectedItem(self.section.getName()) else : self.sectionSwing.setSelectedItem("") self.resistiveSwing.setSelected(self.resistiveWheels) self.canStopAtBeginningSwing.setSelected(self.canStopAtBeginning) self.locoRoster.setSelectedItem(self.locoName) self.reversedSwing.setSelected(self.reversed) # Display schedule self.scheduleSwing.setText(self.schedule.text) def enableSwing(self) : # Enable-disable SWING input fields, depending on train status # Since self.running can change while processing, # use a local copy of it in order to obtain consistent output stopped = not self.running self.nameSwing.setEnabled(stopped) self.directionSwing.setEnabled(stopped) self.sectionSwing.setEnabled(stopped) self.resistiveSwing.setEnabled(stopped) self.canStopAtBeginningSwing.setEnabled(stopped) self.trainLengthSwing.setEnabled(stopped) self.trainAllocationSwing.setEnabled(stopped) self.engineerSwing.setEnabled(stopped) if self.engineerName == "Manual" : self.locoRoster.setEnabled(False) self.reversedSwing.setEnabled(False) else : self.locoRoster.setEnabled(stopped) self.reversedSwing.setEnabled(stopped) self.scheduleSwing.setEnabled(stopped) self.deleteButton.setEnabled(stopped) self.changeButton.setEnabled(stopped) self.setButton.setEnabled(stopped) self.startActionSwing.setEnabled(stopped) def getCurrentBlock(self) : return self.block def getPreviousBlock(self) : return self.previousBlock def getNextBlock(self) : if len(self.blocksAhead) > 0 : return self.blocksAhead[0] return None class ADlocomotive : # Our locomotives. Contain speed tables (i.e. correspondence between # speed levels and throttle settings). A locomotive is created for each # entry in JMRI roster. Additional locomotives are created for demo # layouts # STATIC VARIABLES locoIndex = {} # STATIC METHODS def getNames() : # Return the list of locomotive names return ADlocomotive.locoIndex.keys() getNames = ADstaticMethod(getNames) def getList() : # Return the list of locomotives return ADlocomotive.locoIndex.values() getList = ADstaticMethod(getList) def getByName(name) : # Find a locomotive by name return ADlocomotive.locoIndex.get(name, None) getByName = ADstaticMethod(getByName) # INSTANCE METHODS def __init__(self, name, address, speed, inJmri) : self.name = name ADlocomotive.locoIndex[name] = self self.throttle = None self.leadThrottle = None # Is this locomotive contained in JMRI roster? self.inJmriRoster = inJmri # Throttle not assigned yet self.throttleAssigned = False # Default 128 speed steps self.stepsNumber = 126. self.locoPaused = False self.targetSpeed = 0 self.rampingSpeed = 0 self.savedSpeed = 0 self.usedBy = None self.lastSent = -1L self.leadLoco = 0 # Allow user to define/change address only if locomotive # is not in JMRI roster if inJmri : self.addressSwing = AutoDispatcher.centerLabel("") else : self.addressSwing = JTextField("", 4) self.setAddress(address) # Table of speeds corresponding to each speed level self.speedSwing = [] for ind in range(len(ADsettings.speedsList)) : self.speedSwing.append(JTextField("", 4)) self.setSpeedTable(speed) self.currentSpeedSwing = AutoDispatcher.centerLabel("0") self.accSwing = JTextField("0", 4) self.decSwing = JTextField("0", 4) self.runningTime = 0 self.hoursSwing = JLabel("") self.hoursSwing.setHorizontalAlignment(JLabel.RIGHT) self.warnedTime = False self.mileage = 0.0 self.milesSwing = JLabel("") self.milesSwing.setHorizontalAlignment(JLabel.RIGHT) self.warnedMiles = False self.runningStart = -1L self.setMomentum(0, 0) self.brakeKey = "" self.learningClear() def setSpeedTable(self, speed) : # Change speeds table and display its contents levelsNumber = len(ADsettings.speedsList) if speed == None : # Speed table not defined self.speed = [] # Assign even spaced speeds from 0.01 to 1.0 if levelsNumber > 1 : step = 0.99 / float(levelsNumber - 1) else : step = 0. level = 0.01 for i in range(levelsNumber) : self.speed.append(round(level,2)) level += step else : # Speed table defined # Use it self.speed = speed # Extend table, if needed while len(self.speed) < levelsNumber : self.speed.append(1.) # Update swing fields for i in range(levelsNumber) : self.speedSwing[i].setText(str(self.speed[i])) def getCloserLevel(self, throttleValue) : # Return level corresponding to a given throttle setting. # If no exact match is found, the higher closer level is returned. # Returned value is in range 1-levelsNumber. levelsNumber = len(ADsettings.speedsList) closerLevel = levelsNumber ind = 1 for s in self.speed : if s >= throttleValue : closerLevel = ind break ind += 1 if closerLevel > levelsNumber : return levelsNumber return closerLevel def setMomentum(self, acceleration, deceleration) : if acceleration < 0 : acceleration = 0 if acceleration > 255 : acceleration = 255 if deceleration < 0 : deceleration = 0 if deceleration > 255 : deceleration = 255 self.acceleration = acceleration self.deceleration = deceleration # Compute acceleration/deceleration rate # Same as NMRA DCC CV3 and CV4 if acceleration == 0 : self.accStep = 0 else : try : self.accStep = 1./(float(acceleration) * 8.96) except : self.accStep = self.acceleration = 0 if deceleration == 0 : self.decStep = 0 else : try : self.decStep = 1./(float(deceleration) * 8.96) except : self.decStep = self.deceleration = 0 self.accSwing.setText(str(self.acceleration)) self.decSwing.setText(str(self.deceleration)) def getAcceleration(self) : return self.accStep * 10 def getDeceleration(self) : return self.decStep * 10 def setAddress(self, address) : # Change dcc address if self.throttle != None : self.throttle.release() if address <1 : address = 1 self.address = address self.addressSwing.setText(str(address)) self.throttleAssigned = False def assignThrottle(self) : # Ignore call if throttle already assigned if self.throttleAssigned : return self.throttleAssigned = True # Assign the throttle AutoDispatcher.message("Acquiring throttle for locomotive " + self.name + " (" + str(self.address) + ")") if (self.address > 100) : long = True else : long = False self.throttle = AutoDispatcher.instance.getThrottle(self.address, long) if (self.throttle == None) : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Couldn't assign throttle " + str(self.address) + "!") else : if self.leadLoco != 0 : AutoDispatcher.message("Acquired throttle for consist " + self.name + " (" + str(self.address) + ")") self.leadThrottle = AutoDispatcher.instance.getThrottle( self.leadLoco, long) if (self.leadThrottle != None) : AutoDispatcher.message("Acquired throttle for consist " + self.name + " (" + str(self.leadLoco) + ")") else : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Couldn't assign throttle" + str(self.leadLoco)+ " for consist " + self.name + "!") else : AutoDispatcher.message("Acquired throttle for locomotive " + self.name + " (" + str(self.address) + ")") self.leadThrottle = self.throttle speedStepMode = self.throttle.getSpeedStepMode() if speedStepMode == DccThrottle.SpeedStepMode128 : self.stepsNumber = 126. elif speedStepMode == DccThrottle.SpeedStepMode28 : self.stepsNumber = 28. elif speedStepMode == DccThrottle.SpeedStepMode27 : self.stepsNumber = 27. else : self.stepsNumber = 14. def releaseThrottle(self) : if self.throttle != None : if ADsettings.lightMode != 0 : self.setFunction(0, False) self.throttle.release() self.throttle = None self.throttleAssigned = False if self.leadLoco != 0 : self.leadThrottle.release() AutoDispatcher.message("Released throttles of consist " + self.name + " (" + str(self.address) + ", " + str(self.leadLoco) + ")") else : AutoDispatcher.message("Released throttle of locomotive " + self.name + " (" + str(self.address) + ")") self.leadThrottle = None def outputMileage(self) : # Output values of mileage and operation hours minutes = int(self.runningTime/60000) hours = int(minutes/60) minutes -= hours * 60 if minutes < 10 : minutes = "0" + str(minutes) else : minutes = str(minutes) self.hoursSwing.text = str(hours) + ":" + minutes if self.warnedTime : self.hoursSwing.setForeground(Color.red) else : self.hoursSwing.setForeground(Color.black) if ADsettings.units == 25.4 : multiplier = ADsettings.scale / 1609344. else : multiplier = ADsettings.scale / 1000000. self.milesSwing.text = str(round(self.mileage * multiplier,1)) if self.warnedMiles : self.milesSwing.setForeground(Color.red) else : self.milesSwing.setForeground(Color.black) def setOrientation(self, forward) : # Set locomotive direction if self.throttle != None : self.throttle.setIsForward(forward) if ADsettings.dccDelay > 0 : sleep(float(ADsettings.dccDelay)/1000.) def setFunction(self, functionNumber, on) : if self.leadThrottle != None : if functionNumber < 0 or functionNumber > 28 : return command = "self.leadThrottle.setF" + str(functionNumber) if on : command += "(True)" else : command += "(False)" exec(command) if ADsettings.dccDelay > 0 : sleep(float(ADsettings.dccDelay)/1000.) def changeSpeed(self, speedLevel) : # Stop ? if speedLevel <= 0 : self.changeThrottleSpeed(speedLevel) if (self.usedBy != None and self.usedBy.running and self.usedBy.engineerSetLocomotive != None and not AutoDispatcher.simulation) : self.usedBy.stop() return # Speed increase/decrease - speedControl thread will take care of it if speedLevel > len(self.speed) : speedLevel = len(self.speed) self.changeThrottleSpeed(self.speed[speedLevel-1]) def changeThrottleSpeed(self, speed) : if self.locoPaused : self.savedSpeed = speed return if self.targetSpeed == speed : return self.targetSpeed = speed if speed <= 0 : # STOP if (ADsettings.stopMode != ADsettings.PROGRESSIVE_STOP or speed < 0) : speed = -1 if self.throttle != None : self.throttle.setSpeedSetting(speed) self.targetSpeed = self.rampingSpeed = 0 self.currentSpeedSwing.setText("0") self.updateMeter() elif self.runningStart == -1L : self.runningStart = System.currentTimeMillis() if not AutoDispatcher.paused and not AutoDispatcher.stopped : AutoDispatcher.message("Locomotive " + self.name + " at speed " + str(self.targetSpeed)) def updateMeter(self) : # Updates locomotive's operation time # Called when the locomotive is stopped if self.runningStart != -1L : self.runningTime += System.currentTimeMillis() - self.runningStart self.runningStart = -1L def getName(self) : return self.name def getThrottleSpeed(self) : return self.rampingSpeed def pause(self) : self.savedSpeed = self.targetSpeed if ADsettings.pauseMode == ADsettings.STOP_TRAINS : self.changeThrottleSpeed(0) else : self.changeThrottleSpeed(-1) self.locoPaused = True # Clear learning data, since they are now meaningless self.brakeAdjusting = False def resume(self) : self.locoPaused = False self.changeThrottleSpeed(self.savedSpeed) # Self-learning methods of the locomotive ============== def learningClear(self) : # Variables for self-learning braking method self.brakeAdjusting = False self.delay = 0 self.decAdjustment = 0. self.initialTime = -1L self.initialSpeed = 0. self.brakingStartTime = -1L self.brakingSpeed = 0 self.brakingEndTime = -1L self.stoppingTime = -1L def learningBrake(self) : # Train entering a braking block self.length = 0 if (not ADsettings.selfLearning or self.decStep == 0 or self.usedBy == None or self.throttle == None or self.brakeAdjusting or AutoDispatcher.simulation) : # Self-learning not supported or already active return False # Make sure we have all information needed block = self.usedBy.block previousBlock = self.usedBy.previousBlock train = self.usedBy if block == None or previousBlock == None or train == None : return False self.learningClear() # Take note of time and speed self.initialTime = System.currentTimeMillis() self.initialSpeed = self.rampingSpeed # Since we must brake, make sure that speed is not being increased if self.targetSpeed > self.rampingSpeed : self.targetSpeed = self.rampingSpeed # Build identification key self.brakeKey = (self.name + "$" + block.getName() + "$" + previousBlock.getName()) # Did we already stop in this block? if train.brakingHistory.has_key(self.brakeKey) : # Yes retrieve previous data data = train.brakingHistory[self.brakeKey] self.recordedValues = data[0] self.squareSum = data[1] else : # First time we are braking in this block # Initialize data self.recordedValues = 0 self.squareSum = 0 # Compute braking data # (if we have needed info and we are not running yet at minimimum speed) if self.recordedValues > 0 and self.targetSpeed > self.speed[0] : self.length = sqrt(self.squareSum/float(self.recordedValues)) # Reduce length, in order to reach minimum speed 2 seconds before # reaching the stop block length = self.length - self.speed[0] * 2. if length > 0. : # How much time is required to reach minimum speed? brakeTime = ((self.initialSpeed - self.speed[0]) / self.decStep * 100.) # How much space is required to reach minimum speed? brakeLength = ((self.initialSpeed + self.speed[0]) * brakeTime / 2000.) # Is block length sufficient? delaySpace = length - brakeLength if delaySpace < 0 : # No, we must reduce momentum # Compute time required to break # try brakeTime = (length * 2000. / (self.initialSpeed + self.speed[0])) # Compute acceleration self.decAdjustment = ((self.initialSpeed - self.speed[0]) * 100. / brakeTime) - self.decStep self.brakeAdjusting =True else : # Enough space # How much time is required to reach the target speed? targetTime = ((self.initialSpeed - self.targetSpeed) / self.decStep *100.) # Let's compute braking delay self.delay = int(delaySpace * 1000. / self.targetSpeed + targetTime) if self.delay > 0 : start_new_thread(self.learningDelayedBrake, (self.brakeKey,)) self.brakeAdjusting = True return True else : self.brakeAdjusting = True # We don't have previous data or something went wrong # Let's locomotive start braking immediately self.brakingStartTime = self.initialTime self.brakingSpeed = self.initialSpeed return False def learningDelayedBrake(self, key) : # Wait before braking sleep(float(self.delay)/1000.) # Make sure that braking is still needed if (self.delay > 0 and self.stoppingTime == -1L and self.brakingStartTime == -1L and key == self.brakeKey) : self.brakingStartTime = System.currentTimeMillis() self.brakingSpeed = self.rampingSpeed if self.usedBy == None : self.changeSpeed(1) else : self.usedBy.changeSpeed(1) def learningEnd(self) : # Train completed braking if self.brakeAdjusting and self.brakingEndTime == -1L : self.brakingEndTime = System.currentTimeMillis() def learningStop(self) : # Train entering a stop block # Were we controlling braking times? if not self.brakeAdjusting : # No, ignore call return # Yes, take note of stop time self.stoppingTime = System.currentTimeMillis() # Recompute braking data # (Computation are made assuming a constant speed/throttle-setting # ratio. This is seldom true, but each iteration will improve results) # Evaluate block length, based on speeds and timing # Computed length is expressed as throttle-setting * time and does # thus not refer to the actual length in inches or mm. brakingTime = creepingTime = 0 # Did train start braking (at least!) if self.brakingStartTime == -1L : # No - braking delay was excessive! # Set length to half the length of block computed # in previous iteration (at least for the time being. A more # accurate computation can be implemented later) if AutoDispatcher.debug : print (self.brakeKey + " Delay " + str(self.delay) + " Adjustment " + str(self.decAdjustment) + " length " + str(self.length) + " braking not started") length = self.length * 0.5 else : # Yes, train started braking # Compute distance at full speed length = (float(self.brakingStartTime - self.initialTime) * (self.brakingSpeed + self.initialSpeed) / 2000.) # Did train attain minimum speed? if self.brakingEndTime == -1L : # No, set end of braking time to stopping time if AutoDispatcher.debug : print (self.brakeKey + " Delay " + str(self.delay) + " Adjustment " + str(self.decAdjustment) + " length " + str(length) + " minimum not reached") self.brakingEndTime = self.stoppingTime if length > self.length : length = self.length length = length * 0.5 else : # Yes, minimum speed attained # Compute distance run at minimum speed creepingTime = self.stoppingTime - self.brakingEndTime length += (self.rampingSpeed * float(creepingTime) / 1000.) if AutoDispatcher.debug : print (self.brakeKey + " Delay " + str(self.delay) + " Adjustment " + str(self.decAdjustment) + " length " + str(length) + " creepingTime " + str(creepingTime)) # Now add the length of the braking ramp (braking distance) brakingTime = self.brakingEndTime - self.brakingStartTime length += ((self.brakingSpeed + self.rampingSpeed) * float(brakingTime) / 2000.) # Update braking data if brakingTime >=0 and creepingTime >= 0 and length > 0 : self.recordedValues += 1 self.squareSum += length * length train = self.usedBy if train != None : train.brakingHistory[self.brakeKey] = [self.recordedValues, self.squareSum] # Clear data self.brakeKey = "" self.brakeAdjusting = False # ENGINEER ============== class ADengineer : # Our engineer is rather simple. It only lets know AutoDispatcher that # the ADlocomotive class should be used. ADlocomotive takes care of all # the rest. def setLocomotive(self, locomotive) : return # ROUTE ============== class ADautoRoute : # Defines a route, i.e. the list of entries connecting two sections def __init__(self, startSection, endSection, direction, switching) : # Train direction self.direction = direction # Is train running in switching mode? self.switching = switching # Error indicator. Set if destination is "transit-only" self.error = None # List of blocks contained in the route (filled by setTurnouts method) self.blocksList = [] # Create an array to keep note of which sections were "visited" # in order to avoid an endless loop self.searchedSections = [] # And now search - step will contain the list of entries found self.step = self.__fromTo__(startSection, endSection, direction) if self.found : self.error = None def __fromTo__(self, startSection, endSection, direction): # Internal method. Recursively explores all possibe routes. # If more than one route is found the shorter one (i.e. that with # less steps) is returned. self.found = False # Exit if this section was already "visited" (to avoid an endless loop!) if startSection in self.searchedSections : return [] # Check if direction is acceptable for this section or if train is # in "switching mode" (switching trains can enter any track) if (((direction + 1) & startSection.direction) == 0 and not self.switching) : return [] # Did we reach destination? if startSection == endSection : # Check if destination is transit-only if endSection.transitOnly[direction] and not self.switching : # Transit only destination self.error = endSection else : self.found = True return [] # Mark this section as "visited" (to avoid an endless loop!) self.searchedSections.append(startSection) route = [] routeLen = 0 ## Get next sections in the desired direction for entry in startSection.getEntries(direction) : nextSection = entry.getExternalSection() # Compute direction along next section if entry.getDirectionChange() : newDirection = not direction else : newDirection = direction # Go ahead exploring newRoute = self.__fromTo__(nextSection, endSection, newDirection) if self.found : # A possible route found # Is it the first one found, or is it shorter than # previous routes? newLen = len(newRoute) + 1 if routeLen == 0 or newLen < routeLen : # Yes, keep it route = [entry] route.extend(newRoute) routeLen = newLen # Allow this section to be considered in alternative routes self.searchedSections.pop() self.found = routeLen > 0 return route def reduce(self, train, allocationAhead): # Reduces the length of the route (i.e. number of sections # contained in it) to the minimum between: # its total length; # allocationAhead value. # Unless the train runs in "switching mode", the reduced # route is always terminated with a non transit-only section # (making the route longer or shorter, as needed) in order to # avoid (as far as possible) "grid lock" situations. # The route is anyway terminated before the first occupied section # encountered, unless: # The occupied section is transit-only and # "burst mode" is enabled # (i.e. its direction name is suffixed by "+) and # the section is occupied by a train running in the same # direction of the present train and the first non # transit-only section encountered after it is free. keep = lastFreeSection = 0 firstTime = True # Number of sections with signal (or not transit-only) encountered nSections = 0 previousBurst = False direction = self.direction ind = 1 # Process entries contained in the route for entry in self.step : # Check if possible Xovers are available if not entry.areXoversAvailable() : # An Xover is allocated to another train. Terminate the route break # Get the corresponding section section = entry.getExternalSection() # Adjust train direction, if needed if entry.getDirectionChange() : direction = not direction # Is use of this section allowed ? if (section.isManual() or (not section.transitOnly[direction] and ADgridGroup.lockRisk(train, entry.getInternalSection(), section))) : # No break # get the train (if any) to which the section is allocated otherTrain = section.getAllocated() # Check if the section can be used by present train if (not section.isAvailable() and (otherTrain != train or section.isOccupied())) : # The section is occupied or allocated to another train # Is this the first section along the route or is # "burst" mode not allowed? if (firstTime or (not section.burst and not previousBurst)) : # Present train cannot use it break # Is a train using this section? if otherTrain == None : # No train, section contains only rolling stock. # Present train cannot use it break # We have another train: is it running in the same direction? if otherTrain.getDirection() != direction : # Other train in opposite direction, present train cannot start break # If this is not a Transit-Only section, train can run up to # previous empty section if not section.transitOnly[direction] : keep = lastFreeSection break else : # Take note that section is free (or allocated to this train # but not occupied yet) lastFreeSection = ind # Count free sections encountered and equipped with exit signal if (not section.transitOnly[direction] or section.getSignal(direction).hasHead()) : nSections += 1 # Could the reduced route end in this section? if not section.transitOnly[direction] or self.switching : # The section is not transit-only (or train # in switching mode). # Terminate the route here if the requested number # of sections ahead was found keep = lastFreeSection if nSections >= allocationAhead : break # Take note that we already encountered a section firstTime = False # PreviousBurst temporarily suppressed (could be implemented # as an option) # previousBurst = section.burst # Make sure signal is not held if section.getSignal(direction).isHeld() : break ind += 1 # Job almost done. "keep" contains the number of entries to be kept # Remove possible trailing entries from the route while len(self.step) > keep : self.step.pop() return nSections def allocate(self, train): # Allocates all sections contained in the (reduced) route, # further reducing the route if part of it is occupied by # another train (even if running in the same direction) keep = 0 nSections = 0 freeRoute = True direction = self.direction destination = None ind = 1 # Process all entries in the route for entry in self.step : # Adjust direction, if needed if entry.getDirectionChange() : direction = not direction # Retrieve relevant section section = entry.getExternalSection() # Make sure section is not occupied or allocated if section.isAvailable() : # Allocate sections in front of other trains (if any) # only if they are not transit-only if freeRoute or not section.transitOnly[direction] : section.allocate(train, direction) elif section.getAllocated() != train : # Section occupied or allocated to another train freeRoute = False if section.getAllocated() == train : train.lastRouteSection = section # Take note of last not occupied section if freeRoute : keep = ind if (not section.transitOnly[direction] or section.getSignal(direction).hasHead()) : nSections += 1 destination = section if not section in train.sectionsAhead : train.sectionsAhead.append(section) # Make sure signal is not held if section.getSignal(direction).isHeld() : break ind += 1 # Remove trailing elements from the route (if needed) while len(self.step) > keep : self.step.pop() if destination != None : train.destination = destination direction = destination.trainDirection train.finalDirection = direction train.destinationSignal = destination.signal[direction] return nSections def setTurnouts(self): # Set all turnouts contained in the (reduced) route # Record also blocks of the route in blocksList self.blocksList = [] # Before starting, make sure we have a valid route :-) if len(self.step) == 0 : return direction = self.direction # Start setting turnouts from stop block of starting section startBlock = self.step[0].getInternalSection().stopBlock[direction] for entry in self.step : endBlock = entry.getInternalBlock() # Set turnouts up to exit block included thrown = self.__setTurnouts__(startBlock, endBlock, direction) # Set turnouts from present section to next section startBlock = entry.getExternalBlock() if startBlock.setTurnouts(endBlock) : thrown = True # and vice-versa if endBlock.setTurnouts(startBlock) : thrown = True # Keep note if some turnouts were thrown # (info will be used when clearing signals) entry.getInternalSection().turnoutsThrown = thrown # Adjust train direction, if needed if entry.getDirectionChange() : direction = not direction # Set turnouts from entry of last section to its stop block endBlock = startBlock.getSection().stopBlock[direction] if self.__setTurnouts__(startBlock, endBlock, direction) : # Keep note that some turnouts were thrown startBlock.getSection().turnoutsThrown = True def __setTurnouts__(self, startBlock, endBlock, direction) : # Internal method # Sets turnouts between two blocks of the same section # Returns True if any turnout was thrown. # Ignore call, if start and end block are the same one if startBlock == endBlock : self.blocksList.append(startBlock) return False thrown = False previousBlock = None for block in startBlock.getSection().getBlocks(direction) : # Find starting block if previousBlock == None : if block == startBlock : previousBlock = block self.blocksList.append(block) else: # Starting block found, set turnouts # Note that we need to set turnouts included in both paths: # From previousBlock to block; and # from block to previousBlock. if block.setTurnouts(previousBlock) : thrown = True if previousBlock.setTurnouts(block) : thrown = True self.blocksList.append(block) # Look for ending block if block == endBlock : break previousBlock = block return thrown def clearSignals(self, train): # Clears all signals along the route # Cannot be run before setTurnouts method! # First of all, make a copy of sectionsAhead sections = [] sections.extend(train.previousSections) sections.append(train.section) sections.extend(train.sectionsAhead) # Remove last section (its signal will stay red) sections.pop() # Reverse sections order # (set the farer signal first) sections.reverse() # Next signal is red oldIndication = 0 anyThrown = False processedSections = [] setRed = False for section in sections : if section in processedSections : continue processedSections.append(section) signal = section.getSignal(section.trainDirection) if setRed : signal.setIndication(0) continue if section.turnoutsThrown : anyThrown = True indication = ADindication.getIndication(oldIndication, anyThrown) signal.setIndication(indication) # No need of repainting panel, Layout Editor will do it anyway AutoDispatcher.repaint = False if signal.hasHead() : anyThrown = False oldIndication = indication # Make sure train did not advance in the meantime if section == train.section : setRed = True # SIGNAL HEAD ============== class ADsignalHead : # Our signal head class # Created also if there is no signal head in JMRI def __init__(self, signalName) : self.name = signalName self.signalHead = None self.iconOnLayout = False if signalName.strip() != "" : self.signalHead = InstanceManager.signalHeadManagerInstance( ).getSignalHead(signalName) if self.signalHead != None : self.setHeld(False) self.iconOnLayout = signalName in AutoDispatcher.signalIcons def setAppearance(self, appearance) : # Record new signal appearance and modify also that of the signal head # (if available) self.appearance = appearance if self.signalHead != None : if (not ADsettings.trustSignals or self.signalHead.getAppearance() != self.appearance) : AutoDispatcher.signalCommands[self.signalHead] = [ self.appearance, System.currentTimeMillis()] self.signalHead.setAppearance(self.appearance) # Wait if user specified a delay between signal operation if ADsettings.signalDelay > 0 : AutoDispatcher.instance.waitMsec( ADsettings.signalDelay) def getAppearance(self) : return self.appearance def isHeld(self) : # Checks if the SignalHead (if any) is "HELD" if self.signalHead == None : return False else : return self.signalHead.getHeld() def setHeld(self, newHeld) : # Set the SignalHead (if any) to "HELD" if self.signalHead != None : self.signalHead.setHeld(newHeld) def hasHead(self) : return self.signalHead != None def hasIcon(self) : return self.iconOnLayout # SIGNAL MAST ============== class ADsignalMast : # Our multi-head signal class # Created also if there is no signal head in JMRI # STATIC VARIABLES signalsList = {} # STATIC METHODS def getByName(name) : return ADsignalMast.signalsList.get(name, None) getByName = ADstaticMethod(getByName) def provideSignal(name) : signal = ADsignalMast.getByName(name) if signal == None : signal = ADsignalMast(name, ADsettings.signalTypes[0], [name]) return signal provideSignal = ADstaticMethod(provideSignal) def getNames() : return ADsignalMast.signalsList.keys() getNames = ADstaticMethod(getNames) def getList() : return ADsignalMast.signalsList.values() getList = ADstaticMethod(getList) def getTable() : outBuffer = [] for signal in ADsignalMast.signalsList.values() : outLine = [signal.name] outLine.append(signal.signalType.name) outHeads = [] for h in signal.signalHeads : if h == None : outHeads.append("") else : outHeads.append(h.name) outLine.append(outHeads) outBuffer.append(outLine) return outBuffer getTable = ADstaticMethod(getTable) def putTable(inBuffer) : for inLine in inBuffer : type = ADsettings.signalTypes[0] for newType in ADsettings.signalTypes : if newType.name == inLine[1] : type = newType break ADsignalMast(inLine[0], type, inLine[2]) putTable = ADstaticMethod(putTable) def __init__(self, signalName, signalType, signalHeads) : self.name = signalName self.signalType = signalType self.signalType.changeUse(1) self.inUse = 0 self.headsNumber = self.signalType.headsNumber self.signalHeads = [None] * self.headsNumber ind = 0 for headName in signalHeads : if ind >= self.headsNumber : break self.signalHeads[ind] = ADsignalHead(headName) ind += 1 self.indication = -1 self.setIndication(0) ADsignalMast.signalsList[self.name] = self def setIndication(self, indication) : if self.indication == indication : return self.indication = indication if self.indication >= len(self.signalType.aspects) : return aspects = self.signalType.aspects[indication] for ind in range(self.headsNumber) : if self.signalHeads[ind] != None : self.signalHeads[ind].setAppearance(aspects[ind]) def changeUse(self, increment) : self.inUse += increment if self.inUse < 0 : self.inUse = 0 self.signalType.changeUse(-1) newDic = {} for s in ADsignalMast.getList() : if s != self : newDic[s.name] = s ADsignalMast.signalsList = newDic def getName(self) : return self.name def getIndication(self) : return self.indication def getSpeed(self) : if self.indication >= len(self.signalType.speeds) : return 0 speed = self.signalType.speeds[self.indication] if speed < 0 : if self.indication >= len(ADsettings.indicationsList) : return 0 return ADsettings.indicationsList[self.indication].speed return speed + 1 def setHeld(self, newHeld) : if self.signalHeads > 0 : self.signalHeads[0].setHeld(newHeld) def isHeld(self) : # Checks if any SignalHead is "HELD" for head in self.signalHeads : if head != None : if head.isHeld() : return True return False def hasIcon(self) : # Checks if any SignalHead has an icon for head in self.signalHeads : if head != None : if head.hasIcon() : return True return False def hasHead(self) : if self.headsNumber < 1 or self.signalHeads[0] == None : return False return self.signalHeads[0].hasHead() # SIGNAL INDICATIONS ============== class ADindication : # STATIC METHODS def getIndication(nextIndication, nextTurnout) : next = nextIndication for i in range(3) : for j in range(2, len(ADsettings.indicationsList)) : a = ADsettings.indicationsList[j] if ((a.nextIndication == next or (a.nextIndication >=0 and next >= 0 and ADsettings.indicationsList[a.nextIndication].name == ADsettings.indicationsList[next].name)) and a.nextTurnout == nextTurnout) : return j if (i & 1) == 0 : next = -1 else : next = nextIndication if i == 1 : nextTurnout = -1 return 1 getIndication = ADstaticMethod(getIndication) def __init__(self, name, nextIndication, nextTurnout, speed) : self.name = name self.nextIndication = nextIndication self.nextTurnout = nextTurnout if self.nextTurnout < -1 : self.nextTurnout = -1 elif self.nextTurnout > 1 : self.nextTurnout = 1 self.speed = speed self.nameSwing = JTextField(self.name, 20) self.nextIndicationSwing = JComboBox() self.nextTurnoutSwing = JComboBox(["-", "Closed", "Thrown"]) self.nextTurnoutSwing.setSelectedIndex(self.nextTurnout+1) self.speedSwing = JComboBox() # SIGNAL TYPE ============== class ADsignalType : # STATIC METHODS def adjust() : for s in ADsettings.signalTypes : s.adjustIndications() adjust = ADstaticMethod(adjust) # INSTANCE METHODS def __init__(self, name, aspects, speeds) : # aspects = [[aspect0, aspect1...],...] self.name = name self.inUse = 0 # Compute number of signal heads self.headsNumber = 1 for a in aspects : if len(a) > self.headsNumber : self.headsNumber = len(a) # Compute number of aspects if ADsettings == None : self.aspectsNumber = 2 else : self.aspectsNumber = len(ADsettings.indicationsList) if len(aspects) > self.aspectsNumber : self.aspectsNumber = len(aspects) # Initialize aspects for each head # Default setting for stop = all heads RED self.aspects = [[SignalHead.RED] * self.headsNumber] # Default setting for other indications = all heads GREEN for i in range(self.aspectsNumber-1) : self.aspects.append([SignalHead.GREEN] * self.headsNumber) # Set actual aspects i = 0 for a in aspects : j = 0 for aa in a : self.aspects[i][j] = aa j += 1 i += 1 self.speeds = [-1] * self.aspectsNumber i = 0 for s in speeds : if i >= self.aspectsNumber : break self.speeds[i] = s i += 1 self.nameSwing = JTextField(self.name, 20) def adjustIndications(self) : diff = len(ADsettings.indicationsList) - self.aspectsNumber if diff > 0 : for i in range(diff) : self.aspects.append([SignalHead.GREEN] * self.headsNumber) self.speeds.append(-1) self.aspectsNumber = len(ADsettings.indicationsList) def changeUse(self, increment) : self.inUse += increment if self.inUse < 0 : self.inUse = 0 # SETTINGS ============== class ADsettings : # Contains script settings, in a format suitable to save-retrieve them from preferences file # CONSTANTS # Pause modes IGNORE = 0 STOP_TRAINS = 1 EMERGENCY_STOP_TRAINS = 2 POWER_OFF = 3 ATTENTION_SOUND = 0 START_STOP_SOUND = 1 DERAILED_SOUND = 2 STALLED_SOUND = 3 LOST_CARS_SOUND = 4 WRONG_ROUTE_SOUND = 5 # Color names dictionary colors = {"BLACK": Color.BLACK, "BLUE": Color.BLUE, "CYAN": Color.CYAN, "DARK_GRAY": Color.DARK_GRAY, "GRAY": Color.GRAY, "GREEN": Color.GREEN, "LIGHT_GRAY": Color.LIGHT_GRAY, "MAGENTA": Color.MAGENTA, "ORANGE": Color.ORANGE, "PINK": Color.PINK, "RED": Color.RED, "WHITE": Color.WHITE, "YELLOW": Color.YELLOW} # Default sounds (None = 0) # Default sound names soundLabel = ["Attention", "Script start/stop", "Derailed train", "Stalled train", "Lost cars", "Wrong route"] # Detection action DETECTION_DISABLED = 0 DETECTION_WARNING = 1 DETECTION_PAUSE = 2 # Stop Mode PROGRESSIVE_STOP = 0 IMMEDIATE_STOP = 1 # STATIC VARIABLE - Current settings # Default directions directionNames =("CCW", "CW") ccwStart = "" ccwEnd = "" ccw = 0 # Measurement units 1.0=mm. 10.0=cm. 25.4=inches if Locale.getDefault().getCountry() == "US" : units = 25.4 else : units = 1.0 useLength = False max_trains = 0 allocationAhead = 1 blockTracking = False verbose = False ringBell = True pauseMode = STOP_TRAINS derailDetection = DETECTION_PAUSE wrongRouteDetection = DETECTION_PAUSE # Maximum idle time between speed commands maxIdle = 60 useCustomColors = True # Default section colors (as strings) colorTable = ["BLACK", "BLUE", "RED", "YELLOW", "ORANGE", "MAGENTA", "CYAN"] sectionColor = None useCustomWidth = True trustTurnouts = True # Delay between turnouts operations turnoutDelay = 1000 trustSignals = True # Delay between signals operations signalDelay = 0 # Delay before clearing signals clearDelay = 0 # table of sections' settings in human readable format sections = [] # table of blocks' settings in human readable format blocks = [] # Speeds speedsList = ["Min.", "Low", "Med.", "High", "Max."] dccDelay = 10 startDelayMin = 0 # Speed change frequency (in 1/10h of second) speedRamp = 2 # Ligths Mode: # 0 = No lights; # 1 = ON/OFF when train starts/stops; # 2 = ON/OFF when schedule starts/ends lightMode = 1 indicationsList = [] signalTypes = [] startDelayMax = 0 separateTurnouts = False separateSignals = False stalledDetection = DETECTION_WARNING stalledTime = 60000. selfLearning = False stopMode = IMMEDIATE_STOP lostCarsDetection = DETECTION_WARNING if units == 25.4 : lostCarsTollerance = 2032.0 else : lostCarsTollerance = 2000.0 lostCarsSections = 3 sectionTracking = False soundList = [] defaultSounds = [1] * len(soundLabel) soundRoot = XmlFile.userFileLocationDefault() soundDic = {} maintenanceTime = 0. maintenanceMiles = 0. scale = 87 flashingCycle = 1.0 resistiveDefault = False defaultStartAction = "" autoRestart = False # STATIC METHODS def getSpeedName(speedLevel) : if speedLevel == 0 : return "Stop" if speedLevel > len(ADsettings.speedsList) : return "Unknown: " + str(speedLevel) return ADsettings.speedsList[speedLevel-1] getSpeedName = ADstaticMethod(getSpeedName) def getScale() : return ADsettings.scale getScale = ADstaticMethod(getScale) def getUnits() : return ADsettings.units getUnits = ADstaticMethod(getUnits) def stringToColor(c) : # Convert a string into a color if c.startswith("R:") : # Custom RGB color r = int(c[2:5]) g = int(c[7:10]) b = int(c[12:]) return Color(r, g, b) # Standard Java color return ADsettings.colors[c] stringToColor = ADstaticMethod(stringToColor) def rgbToString(rgb) : # Build a color string "R:rrrG:gggB:bbb" lab = ["R:", "G:", "B:"] out = "" for j in range(3) : out += lab[j] c = rgb[j] if c < 100 : out += "0" if c < 10 : out += "0" out += str(c) return out rgbToString = ADstaticMethod(rgbToString) def initColors() : # Convert colors from strings to JAVA constants ADsettings.sectionColor = [] for c in ADsettings.colorTable : ADsettings.sectionColor.append(ADsettings.stringToColor(c)) initColors = ADstaticMethod(initColors) def save(file, sections, blocks) : outIndications = [] for indication in ADsettings.indicationsList : outIndications.append([indication.name, indication.nextIndication, indication.nextTurnout, indication.speed]) outSignalTypes = [] for signal in ADsettings.signalTypes : signalLine = [signal.name] indicationLines = [] for i in range(len(ADsettings.indicationsList)) : indicationLine = [] for aa in signal.aspects[i] : indicationLine.append(AutoDispatcher.inverseAspects[aa]) indicationLines.append(indicationLine) signalLine.append(indicationLines) signalLine.append(signal.speeds) outSignalTypes.append(signalLine) sounds = [] for s in ADsettings.soundList : sounds.append([s.name, s.path]) locations = [] for l in ADlocation.getList() : locations.append([l.name, l.text]) outData = (AutoDispatcher.version, ADsettings.directionNames, ADsettings.ccwStart, ADsettings.ccwEnd, ADsettings.turnoutDelay, ADsettings.max_trains, ADsettings.allocationAhead, ADsettings.verbose, ADsettings.pauseMode, ADsettings.derailDetection, ADsettings.wrongRouteDetection, ADsettings.maxIdle, ADsettings.useCustomColors, ADsettings.colorTable, ADsettings.useCustomWidth, sections, blocks, ADsettings.trustTurnouts, ADsettings.trustSignals, ADsettings.signalDelay, ADsettings.units, ADsettings.clearDelay, ADsettings.useLength, ADsettings.blockTracking, ADsettings.speedsList, ADsettings.resistiveDefault, ADsettings.dccDelay, ADsettings.startDelayMin, ADsettings.speedRamp, ADsettings.lightMode, outIndications, ADsettings.ringBell, outSignalTypes, ADsignalMast.getTable(), ADsettings.startDelayMax, ADsettings.separateTurnouts, ADsettings.separateSignals, ADsettings.stalledDetection, ADsettings.stalledTime, ADsettings.selfLearning, ADsettings.stopMode, ADsettings.lostCarsDetection, ADsettings.lostCarsTollerance, ADsettings.lostCarsSections, ADsettings.sectionTracking, sounds, ADsettings.defaultSounds, ADsettings.soundRoot, ADsettings.maintenanceTime, ADsettings.maintenanceMiles, ADsettings.scale, locations, ADsettings.flashingCycle, ADsettings.defaultStartAction, ADsettings.autoRestart) file.writeObject(outData) save = ADstaticMethod(save) def load(inData) : creationVersion = inData[0] ADsettings.directionNames = inData[1] ADsettings.ccwStart = inData[2] ADsettings.ccwEnd = inData[3] ADsettings.turnoutDelay = inData[4] ADsettings.max_trains = inData[5] ADsettings.allocationAhead = inData[6] ADsettings.verbose = inData[7] ADsettings.pauseMode = inData[8] ADsettings.derailDetection = inData[9] ADsettings.wrongRouteDetection = inData[10] ADsettings.maxIdle = inData[11] ADsettings.useCustomColors = inData[12] ADsettings.colorTable = inData[13] ADsettings.useCustomWidth = inData[14] ADsettings.sections = inData[15] ADsettings.blocks = inData[16] ADsettings.trustTurnouts = inData[17] ADsettings.trustSignals = inData[18] ADsettings.signalDelay = inData[19] ADsettings.units = inData[20] ADsettings.clearDelay = inData[21] if ADblock.blocksWithLength > 0 : ADsettings.useLength = inData[22] ADsettings.blockTracking = inData[23] ADsettings.speedsList = inData[24] ADsettings.resistiveDefault = inData[25] ADsettings.dccDelay = inData[26] ADsettings.startDelayMin = inData[27] ADsettings.speedRamp = inData[28] ADsettings.lightMode = inData[29] ADsettings.indicationsList = [] for a in inData[30] : ADsettings.indicationsList.append(ADindication(a[0], a[1], a[2], a[3])) ADsignalType.adjust() ADsettings.ringBell = inData[31] ADsettings.signalTypes = [] for s in inData[32] : indicationLines = [] for a in s[1] : indicationLine = [] for aa in a : indicationLine.append(AutoDispatcher.headsAspects[aa]) indicationLines.append(indicationLine) ADsettings.signalTypes.append(ADsignalType(s[0], indicationLines, s[2])) ADsignalMast.putTable(inData[33]) ADsettings.startDelayMax = inData[34] ADsettings.separateTurnouts = inData[35] ADsettings.separateSignals = inData[36] ADsettings.stalledDetection = inData[37] ADsettings.stalledTime = inData[38] ADsettings.selfLearning = inData[39] ADsettings.stopMode = inData[40] if ADsettings.stopMode > 1 : ADsettings.stopMode = 1 ADsettings.lostCarsDetection = inData[41] ADsettings.lostCarsTollerance = inData[42] ADsettings.lostCarsSections = inData[43] ADsettings.sectionTracking = inData[44] ADsettings.soundList = [] for i in inData[45] : s = ADsound(i[0]) s.setPath(i[1]) ADsettings.soundList.append(s) ADsettings.newSoundDic() ADsettings.defaultSounds = inData[46] while len(ADsettings.defaultSounds) < len(ADsettings.soundLabel) : ADsettings.defaultSounds.append(1) if inData[47] != "" : ADsettings.soundRoot = inData[47] ADsettings.maintenanceTime = inData[48] ADsettings.maintenanceMiles = inData[49] ADsettings.scale = inData[50] for l in inData[51] : location = ADlocation(l[0]) location.setSections(l[1]) ADsettings.flashingCycle = inData[52] if len(inData) > 53 : ADsettings.defaultStartAction = inData[53] if len(inData) > 54 : ADsettings.autoRestart = inData[54] ADsettings.initColors() load = ADstaticMethod(load) def newSoundDic() : newDic = {} for s in ADsettings.soundList : newDic[s.name] = s ADsettings.soundDic = newDic newSoundDic = ADstaticMethod(newSoundDic) # INSTANCE METHODS! def __init__(self) : ADsettings.initColors() ADsettings.indicationsList = [ADindication("Stop", -1, -1, 0), ADindication("Clear", -1, -1, len(ADsettings.speedsList))] ADsettings.signalTypes = [ADsignalType("Single Head", [[SignalHead.RED], [SignalHead.GREEN]], [])] alarmSound = ADsound("Bell") alarmSound.setPath("resources/sounds/bell.wav") ADsettings.soundList = [alarmSound] ADsettings.newSoundDic() # SCHEDULE ============== class ADschedule : # Encapsulates all info relevant to a schedule # CONSTANTS # Action types # Actions that require train stopping ERROR = -2 END_ALTERNATIVE = -1 STOP = 0 PAUSE = 1 START_AT = 2 CCW = 3 CW = 4 WAIT_FOR = 5 IFE = 6 IFAT = 7 IFH = 8 MANUAL_PRESENT = 9 # Actions that can be executed while train is running GOTO = 10 SWON = 11 SWOFF = 12 HELD = 13 RELEASE = 14 SET_F_ON = 15 SET_F_OFF = 16 DELAY = 17 MANUAL_OTHER = 18 SOUND = 19 TC = 20 TT = 21 def __init__(self, text) : # Save original text self.text = text self.source = [] textSpace = text.replace(",", " ") # Break down input text into tokens splitted = textSpace.split() # Break down tokens containing open brackets "(" charList = ")[]" for s in splitted : i=s.find("(") while i >= 0 and i < len(s)-1 : self.__split(s[:i+1], charList) s = s[i+1:] i=s.find("(") self.__split(s, charList) self.__clearFields() def __split(self, s, charList) : # Internal method # Recursively breaks down tokens containing special characters # (open/closed brackets) if charList == "" : self.source.append(s) else : term = charList[0] if len(charList) > 1 : charList = charList[1:] else : charList = "" i=s.find(term) while i >= 0 and len(s) > 1 : if i > 0 : self.__split(s[:i], charList) s = s[i:] if len(s) > 1 : self.source.append(term) s = s[1:] i=s.find(term) self.__split(s, charList) def __clearFields(self) : # Set initial status of fields, in order to start schedule scanning self.pointer = 0 self.iteration = 0 self.iterations = 0 self.stack = [] self.error = False self.alternative = False self.repeating = False self.test = False self.condition = True self.ifStart = False self.endAlternative = 0 self.currentItem = self.__getNextItem() self.looping = False def __getNextItem(self) : # Get next item in the schedule (internal method) self.firstCall = True newItem = ADscheduleItem() # Are we at the beginning of a $IF? if self.ifStart : # Skip commands until condition becomes True count = 0 while not self.condition : if self.pointer >= len(self.source) : self.error = True newItem.action = ADschedule.ERROR newItem.message = "$IF not closed by $END" return newItem sl = self.source[self.pointer] s = sl.upper() self.pointer += 1 if s.startswith("$IF") : # Nested IF count += 1 elif s == "$END" : # Decrease number of nested IFs count -= 1 if count < 0 : # End of main IF reached self.pop() self.condition = True elif s == "$ELSE" : if count == 0 : # ELSE of main IF reached self.condition = True self.ifStart = False # End of schedule? if self.pointer >= len(self.source) : return newItem # No, get next token? sl = self.source[self.pointer] s = sl.upper() self.pointer += 1 if s.endswith("(") : # Start of repetition self.push() self.test = False self.alternative = False self.repeating = True self.iteration = 0 if len(s) > 1 : s = s[:len(s)-1] try : self.iterations = int(s) except : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Wrong value \"" + sl +"\"" return newItem return self.__getNextItem() if s == ")" : # End of repetition if not self.repeating : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Unbalanced close bracket \")\"" self.looping = True return newItem # Should we iterate? self.iteration += 1 # Is this an endless loop? self.looping = self.iterations == 0 # Should we repeat? if self.looping or self.iteration < self.iterations : # Repeat again self.pointer = self.stack[len(self.stack)-1] if self.looping : self.iteration = 0 return self.__getNextItem() # Repetition completed self.pop() return self.__getNextItem() if s == "[" : # Start of alternative if self.alternative : newItem.action = ADschedule.ERROR newItem.message = "Nested square brackets \"[\" are not supported!" self.looping = True return newItem self.push() self.alternative = True self.test = False self.repeating = False return self.__getNextItem() if s == "]" : # End of alternative if not self.alternative : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Unbalanced close bracket \"]\"" return newItem self.endAlternative = self.pointer self.pointer = self.stack[len(self.stack)-1] newItem.action = ADschedule.END_ALTERNATIVE return newItem # Test for $ prefixed commands if s.startswith("$IF") : # Start of test self.push() self.test = True self.alternative = False self.repeating = False i=s.find(":") if s.startswith("$IFH") : newItem.action = ADschedule.IFH if s == "$IFH" : newItem.value = None self.condition = True self.ifStart = True return newItem if i < 0 : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Wrong format \"" + sl +"\"" return newItem self.__getSignal(sl, newItem) if newItem.action == ADschedule.ERROR : self.error = True else : self.condition = True self.ifStart = True return newItem if i < 0 : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Wrong format \"" + sl +"\"" return newItem newItem.value = self.__getArgs(sl[i+1:]) if len(newItem.value) == 0 : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Wrong/missing section name \"" + sl +"\"" return newItem if s.startswith("$IFAT:") : newItem.action = ADschedule.IFAT self.condition = True self.ifStart = True return newItem elif s.startswith("$IFE:") : newItem.action = ADschedule.IFE self.condition = True self.ifStart = True return newItem else : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Unknown command \"" + sl +"\"" return newItem if s == "$ELSE" : # Third term of test if not self.test : self.error = True newItem.action = ADschedule.ERROR newItem.message = "$ELSE not preceded by $IF" return newItem self.condition = False # Skip tokens untile $END is found count = 0 while not self.condition : if self.pointer >= len(self.source) : self.error = True newItem.action = ADschedule.ERROR newItem.message = "$IF not closed by $END" return newItem sl = self.source[self.pointer] s = sl.upper() self.pointer += 1 if s.startswith("$IF") : # Nested IF count += 1 elif s == "$END" : # Decrease number of nested IFs count -= 1 if count < 0 : # End of main IF reached self.pop() self.condition = True elif s == "$ELSE" : if count == 0 : # Too many ELSE self.error = True newItem.action = ADschedule.ERROR newItem.message = "$ELSE not preceded by $IF" return newItem return self.__getNextItem() if s == "$END" : # End of test if not self.test : self.error = True newItem.action = ADschedule.ERROR newItem.message = "$END not preceded by $IF" return newItem self.pop() return self.__getNextItem() # $Pn - pause n seconds. $Dn delay n seconds if s.startswith("$P") or s.startswith("$D") : st = s try : st = s[2:] except : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Missing value \"" + sl + "\"" return newItem if st.startswith("M") : try : st = st[1:] except : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Missing value \"" + sl + "\"" return newItem useFastClock = True else : useFastClock = False try : newItem.value = float(st) except : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Wrong value \"" + sl + "\"" return newItem if useFastClock : newItem.value = (newItem.value * 60. / AutoDispatcher.fastBase.getRate()) if s.startswith("$P") : newItem.action = ADschedule.PAUSE else : newItem.action = ADschedule.DELAY return newItem # Direction change if (s == "$CCW" or s == "$EAST" or s == "$NORTH" or s == "$LEFT" or s == "$UP") : newItem.action = ADschedule.CCW return newItem if (s == "$CW" or s == "$WEST" or s == "$SOUTH" or s == "RIGHT" or s == "DOWN") : newItem.action = ADschedule.CW return newItem # Switching mode # Allows train to enter restricted tracks # i.e. ONE-WAY and TRANSIT-ONLY if s == "$SWON" : newItem.action = ADschedule.SWON return newItem if s == "$SWOFF" : newItem.action = ADschedule.SWOFF return newItem # Signals control newItem.action = ADschedule.ERROR if s.startswith("$H:") : # $H:signalName sets a signal to "Held" state newItem.action = ADschedule.HELD self.__getSignal(sl, newItem) self.error = newItem.action == ADschedule.ERROR return newItem elif s.startswith("$R:") : # $R:signalName removes the "Held" state newItem.action = ADschedule.RELEASE self.__getSignal(sl, newItem) self.error = newItem.action == ADschedule.ERROR return newItem # Decoder functions (F0-F28) if s.startswith("$ON:F") : newItem.action = ADschedule.SET_F_ON newItem.value = s[5:] elif s.startswith("$OFF:F") : newItem.action = ADschedule.SET_F_OFF newItem.value = s[6:] if newItem.action != ADschedule.ERROR : # Retrieve $ON $OFF argument (function number) try : newItem.value = int(newItem.value) if newItem.value < 0 or newItem.value > 28 : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Function number out of range \"" + sl + "\"" except : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Wrong/missing function number \"" + sl + "\"" return newItem # Wait for empty section if s.startswith("$WF:") : st = sl try : st = sl[4:] except : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Missing section name \"" + sl + "\"" return newItem newItem.value = ADsection.getByName(st) if newItem.value == None : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Unknown section \"" + sl + "\"" return newItem newItem.action = ADschedule.WAIT_FOR return newItem # Set present section to manual mode if s == "$M" : newItem.action = ADschedule.MANUAL_PRESENT return newItem # Set another section to manual mode if s.startswith("$M:") : try : newItem.value = ADsection.getByName(sl[3:]) except : newItem.value = None if newItem.value == None : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Unknown/missing section \"" + sl + "\"" self.looping = True return newItem newItem.action = ADschedule.MANUAL_OTHER return newItem if s.startswith("$S:") : # Play sound try : newItem.value = ADsettings.soundDic.get(sl[3:], None) except : newItem.value = None if newItem.value == None : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Unknown/missing sound \"" + sl + "\"" return newItem newItem.action = ADschedule.SOUND return newItem # Set turnout if s.startswith("$TC:") or s.startswith("$TT:") : try : newItem.value = sl[4:] except : newItem.value = None if newItem.value == None or newItem.value == "" : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Missing turnout name \"" + sl + "\"" return newItem if s.startswith("$TC:") : newItem.action = ADschedule.TC else : newItem.action = ADschedule.TT return newItem # Start time (using Fast Clock) if s.startswith("$ST:") : try: minutes = hours = 0 time = sl[4:] i=time.find(":") if i < 0 : hours = int(time) else : hours = time[0:i] hours = int (hours) minutes = time[i+1:] minutes = int (minutes) newItem.value = hours * 60 + minutes except : self.error = True newItem.action = ADschedule.ERROR newItem.message = "Wrong time value \"" + sl + "\"" return newItem newItem.action = ADschedule.START_AT return newItem #If no command prefixed by $ was found # argument should be a section name newItem.value = ADsection.getByName(sl) if newItem.value == None : self.error = True newItem.action = ADschedule.ERROR if sl.startswith("$") : newItem.message = "Unknown command \"" + sl + "\"" else : newItem.message = "Unknown section \"" + sl + "\"" return newItem newItem.action = ADschedule.GOTO return newItem def __getArgs(self, arg) : # Get arguments for commands expecting a section name or # a list of section names argList = [] if arg != "" : # Argument is a single section name arg = ADsection.getByName(arg) if arg != None : argList.append(arg) else : # Argument is a list of section names newItem = self.__getNextItem() while newItem.action == ADschedule.GOTO : argList.append(newItem.value) newItem = self.__getNextItem() self.next() self.pointer -=1 self.alternative = False return argList def __getSignal(self, arg, newItem) : # Get argument for commands expecting a signal name i=arg.find(":") try : signalName = arg[i+1:] except : newItem.action = ADschedule.ERROR newItem.message = "Missing signal name \"" + arg + "\"" return # retrieve signal newItem.value = ADsignalMast.getByName(signalName) if newItem.value == None : newItem.action = ADschedule.ERROR newItem.message = "Unknown signal \"" + arg + "\"" return def getNextAlternative(self) : # Loop among alternative destinations # i.e. list of destinations enclosed in square brackets "[...]" outItem = self.currentItem if self.error : return outItem if self.alternative : self.currentItem = self.__getNextItem() if self.error : return self.currentItem elif (not self.firstCall) and self.currentItem.action == ADschedule.GOTO : outItem = ADscheduleItem() outItem.action = ADschedule.END_ALTERNATIVE outItem.value = 0 self.firstCall = not self.firstCall return outItem def next(self) : # step to next item (or next list of alternatives) if self.error : self.currentItem.action = ADschedule.ERROR return if self.alternative : self.pointer = self.endAlternative self.pop() self.currentItem = self.__getNextItem() def getFirstAlternative(self) : # Get the first alternative destination # i.e. first destination enclosed in square brackets "[x...]" if self.error: return self.currentItem if self.alternative : while self.currentItem.action == ADschedule.GOTO : self.currentItem = self.__getNextItem() if self.error: return self.currentItem self.currentItem = self.__getNextItem() else : self.firstCall = True return self.getNextAlternative() def testCondition(self, item, section, direction) : # Set test results for $IFAT and $IFE condition = False if item.action == ADschedule.IFAT : # Test for current section for arg in item.value : if arg == section : condition = True break elif item.action == ADschedule.IFE : # Test for empty section for arg in item.value : if arg.isAvailable() : condition = True break # Set test results for $IFH elif item.action == ADschedule.IFH : signal = item.value if signal == None : signal = section.getSignal(direction) condition = signal.isHeld() else : # No $IF command - return present item return item # Make sure that we are at the beginning of a test if self.ifStart : # Apply test results self.condition = condition # Return next schedule item item.action = ADschedule.END_ALTERNATIVE # return self.getFirstAlternative() return item def push(self) : # Internal method - pushes status into the internal stack self.stack.append(self.ifStart) self.stack.append(self.condition) self.stack.append(self.test) self.stack.append(self.endAlternative) self.stack.append(self.alternative) self.stack.append(self.repeating) self.stack.append(self.iterations) self.stack.append(self.iteration) self.stack.append(self.pointer) def pop(self) : # Internal method - pops status from the internal stack # skip pointer (must be restored manually, if needed) self.stack.pop() self.iteration = self.stack.pop() self.iterations = self.stack.pop() self.repeating = self.stack.pop() self.alternative = self.stack.pop() self.endAlternative = self.stack.pop() self.test = self.stack.pop() self.condition = self.stack.pop() self.ifStart = self.stack.pop() def match(self, section, direction) : # Try to find the first occurence of a section in the schedule self.__clearFields() minDistance = 0 while True : if self.looping : break item = self.getFirstAlternative() item = self.testCondition(item, section, direction) while item.action == ADschedule.GOTO : if item.value == section : # Move to next destination self.next() return # Try and find a route to present destination route = ADautoRoute(section, item.value, direction, False) # Take note of route length routeLength = len(route.step) if (routeLength > 0 and (minDistance == 0 or routeLength < minDistance)) : minDistance = routeLength item = self.getNextAlternative() if item.action == ADschedule.ERROR : self.__clearFields() self.currentItem.action = ADschedule.ERROR self.currentItem.message = item.message self.error = True return if item.action == ADschedule.STOP : break self.next() # No explicit reference to section found self.__clearFields() # Did we find at least one route? if minDistance == 0 : return # At least one route found - synchronize with it while True : if self.looping : # Should not occur break item = self.getFirstAlternative() while item.action == ADschedule.GOTO : # Get the route to present destination route = ADautoRoute(section, item.value, direction, False) # Has it the required length? if len(route.step) == minDistance : return item = self.getNextAlternative() if item.action == ADschedule.ERROR : # Should not occur self.__clearFields() self.currentItem.action = ADschedule.ERROR self.error = True return if item.action == ADschedule.STOP : # Should not occur break self.next() # Just in case (we should never get here) self.__clearFields() class ADscheduleItem : # Encapsulates info relevant to a schedule item def __init__(self) : self.action = ADschedule.STOP self.value = 0 self.message = "" class ADsound : # Encapsulates info relevant to a JMRI Sound def __init__(self, name) : self.name = name self.path = "" self.sound = None def setPath(self, path) : self.path = path if self.path.strip() != "" : try : self.sound = Sound(path) except : self.sound = None def play(self) : if self.sound != None : self.sound.play() class ADpowerMonitor (PropertyChangeListener) : # Monitors power on layout # Or simulates it, if the simulator used (e.g. XpressNet) is not # supporting powerManager # STATIC VARIABLES powerOn = True savePause = False powerOffTime = -1L def __init__(self): self.powerManager = InstanceManager.powerManagerInstance() if self.powerManager != None : ADpowerMonitor.powerOn = (self.powerManager.getPower() == PowerManager.ON) self.powerManager.addPropertyChangeListener(self) def propertyChange(self, ev) : value = self.powerManager.getPower() newStatus = ADpowerMonitor.powerOn if value == PowerManager.ON : newStatus = True elif value == PowerManager.OFF : newStatus = False if newStatus == ADpowerMonitor.powerOn : return ADpowerMonitor.powerOn = newStatus if ADpowerMonitor.powerOn : self.resetAccessories() if ADpowerMonitor.savePause and ADmainMenu.resumeButton.enabled : AutoDispatcher.instance.resume() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Power On") else : ADpowerMonitor.powerOffTime = System.currentTimeMillis() ADpowerMonitor.savePause = ADmainMenu.pauseButton.enabled if ADpowerMonitor.savePause : AutoDispatcher.instance.stopAll() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Power Off") def __del__(self) : self.dispose() def dispose(self) : if self.powerManager != None : self.powerManager.removePropertyChangeListener(self) def setPower(self, power) : ADpowerMonitor.powerOn = power == PowerManager.ON if self.powerManager != None : self.powerManager.setPower(power) if not ADsettings.separateTurnouts or not ADsettings.separateSignals : sleep(1.0) self.resetAccessories() def resetAccessories(self) : if ADpowerMonitor.powerOffTime == -1L : return if not ADsettings.separateTurnouts : checkTime = ADsettings.turnoutDelay * 2 if checkTime < 3000 : checkTime = 3000 checkTime = ADpowerMonitor.powerOffTime - checkTime for turnout in AutoDispatcher.turnoutCommands.keys() : packet = AutoDispatcher.turnoutCommands[turnout] if packet[1] > checkTime : AutoDispatcher.turnoutCommands[turnout] = [packet[0], System.currentTimeMillis()] turnout.setState(packet[0]) # Wait if user specified a delay between turnout operation if ADsettings.turnoutDelay > 0 : sleep(float(ADsettings.turnoutDelay)/1000.) if not ADsettings.separateSignals : checkTime = ADsettings.signalDelay * 2 if checkTime < 3000 : checkTime = 3000 checkTime = ADpowerMonitor.powerOffTime - checkTime for signal in AutoDispatcher.signalCommands.keys() : packet = AutoDispatcher.signalCommands[signal] if packet[1] > checkTime : AutoDispatcher.signalCommands[signal] = [packet[0], System.currentTimeMillis()] signal.setAppearance(packet[0]) # Wait if user specified a delay between signal operation if ADsettings.signalDelay > 0 : sleep(float(ADsettings.signalDelay)/1000.) # USER INTERFACE CLASSES ============== Long and boring :-) # Main window ================= class ADmainMenu (JmriJFrame) : # create frame borders blackline = BorderFactory.createLineBorder(Color.black) spacing = BorderFactory.createEmptyBorder(5, 5, 5, 5) saveSettingsButton = JButton("Save Settings") saveTrainsButton = JButton("Save Trains") startButton = JButton("Start") stopButton = JButton("Stop") pauseButton = JButton("Pause") resumeButton = JButton("Resume") autoButton = JCheckBox("Auto", True) def __init__(self) : # super.init JmriJFrame.__init__(self, "AutoDispatcher " + AutoDispatcher.version) self.addWindowListener(ADcloseWindow()) # Handle window closure # (see last class at the bottom of file) self.contentPane.setLayout(BoxLayout(self.contentPane, BoxLayout.Y_AXIS)) # create frame borders self.contentPane.setBorder(ADmainMenu.spacing) # Set a warning at the top of page temppane = JPanel() temppane.setLayout(GridLayout(2, 1)) l = AutoDispatcher.centerLabel(" WARNING: DO NOT SAVE YOUR PANEL ") l.setForeground(Color.red) temppane.add(l) l = AutoDispatcher.centerLabel(" AFTER RUNNING THIS SCRIPT!!! ") l.setForeground(Color.red) temppane.add(l) self.contentPane.add(temppane) # Settings panel temppane = JPanel() temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline, "Layout settings")) temppane.setLayout(GridLayout(3, 2)) # create the Preferences button self.preferencesButton = JButton("Preferences") self.preferencesButton.enabled = False self.preferencesButton.actionPerformed = self.whenPreferencesClicked temppane.add(self.preferencesButton) # create the Panel button self.panelButton = JButton("Panel") self.panelButton.actionPerformed = self.whenPanelClicked self.panelButton.enabled = False temppane.add(self.panelButton) # create the Direction button self.directionButton = JButton("Direction") self.directionButton.actionPerformed = self.whenDirectionClicked self.directionButton.enabled = False temppane.add(self.directionButton) # create the Sections button self.sectionsButton = JButton("Sections") self.sectionsButton.actionPerformed = self.whenSectionsClicked self.sectionsButton.enabled = False temppane.add(self.sectionsButton) # create the Blocks button self.blocksButton = JButton("Blocks") self.blocksButton.actionPerformed = self.whenBlocksClicked self.blocksButton.enabled = False temppane.add(self.blocksButton) # create the Locations button self.locationsButton = JButton("Locations") self.locationsButton.enabled = False self.locationsButton.actionPerformed = self.whenLocationsClicked temppane.add(self.locationsButton) self.contentPane.add(temppane) temppane = JPanel() temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline, "Signals")) temppane.setLayout(GridLayout(2, 2)) # create the Speeds button self.speedsButton = JButton("Speeds") self.speedsButton.actionPerformed = self.whenSpeedsClicked self.speedsButton.enabled = False temppane.add(self.speedsButton) # create the Signal Indications button self.indicationsButton = JButton("Indications") self.indicationsButton.actionPerformed = self.whenIndicationsClicked self.indicationsButton.enabled = False temppane.add(self.indicationsButton) # create the Signal Types button self.signalTypesButton = JButton("Signal Types") self.signalTypesButton.actionPerformed = self.whenSignalTypesClicked self.signalTypesButton.enabled = False temppane.add(self.signalTypesButton) # create the Signal Masts button self.signalMastsButton = JButton("Signal Masts") self.signalMastsButton.actionPerformed = self.whenSignalMastsClicked self.signalMastsButton.enabled = False temppane.add(self.signalMastsButton) self.contentPane.add(temppane) # Sounds panel temppane = JPanel() temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline, "Sounds")) temppane.setLayout(GridLayout(1, 2)) # create the List button self.soundListButton = JButton("List") self.soundListButton.actionPerformed = self.whenSoundListClicked self.soundListButton.enabled = False temppane.add(self.soundListButton) # create the Default button self.soundDefaultButton = JButton("Default") self.soundDefaultButton.actionPerformed = ( self.whenSoundDefaultClicked) self.soundDefaultButton.enabled = False temppane.add(self.soundDefaultButton) self.contentPane.add(temppane) temppane = JPanel() temppane.setBorder(ADmainMenu.spacing) temppane.setLayout(GridLayout(1, 1)) # create the Save Settings button ADmainMenu.saveSettingsButton.enabled = False ADmainMenu.saveSettingsButton.actionPerformed = ( self.whenSaveSettingsClicked) temppane.add(ADmainMenu.saveSettingsButton) self.contentPane.add(temppane) # temppane.add(JLabel("")) # Operations panel temppane = JPanel() temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline, "Operations")) temppane.setLayout(GridLayout(3, 2)) # create the Start button ADmainMenu.startButton.actionPerformed = self.whenStartClicked ADmainMenu.startButton.enabled = False temppane.add(ADmainMenu.startButton) # create the Stop button ADmainMenu.stopButton.enabled = False ADmainMenu.stopButton.actionPerformed = self.whenStopClicked temppane.add(ADmainMenu.stopButton) # create the Locomotives button self.locoButton = JButton("Locomotives") self.locoButton.actionPerformed = self.whenLocosClicked self.locoButton.enabled = False temppane.add(self.locoButton) # create the Trains button self.trainsButton = JButton("Trains") self.trainsButton.actionPerformed = self.whenTrainsClicked self.trainsButton.enabled = False temppane.add(self.trainsButton) # create the Save Trains button ADmainMenu.saveTrainsButton.actionPerformed = self.whenSaveTrainsClicked ADmainMenu.saveTrainsButton.enabled = False temppane.add(ADmainMenu.saveTrainsButton) temppane.add(JLabel("")) self.contentPane.add(temppane) # Emergency panel temppane = JPanel() temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline, "Emergency")) temppane.setLayout(GridLayout(1, 2)) # create the Pause button ADmainMenu.pauseButton.enabled = False ADmainMenu.pauseButton.actionPerformed = self.whenPauseClicked temppane.add(ADmainMenu.pauseButton) # create the Resume button ADmainMenu.resumeButton.enabled = False ADmainMenu.resumeButton.actionPerformed = self.whenResumeClicked temppane.add(ADmainMenu.resumeButton) self.contentPane.add(temppane) if AutoDispatcher.simulation : # Simulation panel temppane = JPanel() temppane.setBorder(BorderFactory.createTitledBorder( ADmainMenu.blackline, "Simulation")) temppane.setLayout(GridLayout(1, 2)) # create the Step button self.stepButton = JButton("Step") self.stepButton.actionPerformed = self.whenStepClicked self.stepButton.enabled = False temppane.add(self.stepButton) # create the Auto checkbox ADmainMenu.autoButton.enabled = False temppane.add(ADmainMenu.autoButton) self.contentPane.add(temppane) # Status message temppane = JPanel() scrollPane = JScrollPane(AutoDispatcher.statusScroll) scrollPane.setCorner(JScrollPane.LOWER_RIGHT_CORNER, JPanel()) temppane.add(scrollPane) self.contentPane.add(temppane) # Display frame self.pack() self.show() # Main window buttons ================= # Settings buttons # define what Direction button does when clicked def whenDirectionClicked(self,event) : if AutoDispatcher.directionFrame == None : AutoDispatcher.directionFrame = ADdirectionFrame() else : AutoDispatcher.directionFrame.show() # define what Panel button does when clicked def whenPanelClicked(self,event) : if AutoDispatcher.panelFrame == None : AutoDispatcher.panelFrame = ADpanelFrame() else : AutoDispatcher.panelFrame.show() # define what Speeds button does when clicked def whenSpeedsClicked(self,event) : if AutoDispatcher.speedsFrame == None : AutoDispatcher.speedsFrame = ADspeedsFrame() else : AutoDispatcher.speedsFrame.show() # define what Indications button does when clicked def whenIndicationsClicked(self,event) : if AutoDispatcher.indicationsFrame == None : AutoDispatcher.indicationsFrame = ADindicationsFrame() else : AutoDispatcher.indicationsFrame.show() # define what Signal Types button does when clicked def whenSignalTypesClicked(self,event) : if AutoDispatcher.signalTypesFrame == None : AutoDispatcher.signalTypesFrame = ADsignalTypesFrame() else : AutoDispatcher.signalTypesFrame.show() return # define what Signal Masts button does when clicked def whenSignalMastsClicked(self,event) : if AutoDispatcher.signalMastsFrame == None : AutoDispatcher.signalMastsFrame = ADsignalMastsFrame() else : AutoDispatcher.signalMastsFrame.show() return # define what Sections button does when clicked def whenSectionsClicked(self,event) : if AutoDispatcher.sectionsFrame == None : AutoDispatcher.sectionsFrame = ADsectionsFrame() else : AutoDispatcher.sectionsFrame.show() # define what Blocks button does when clicked def whenBlocksClicked(self,event) : if AutoDispatcher.blocksFrame == None : AutoDispatcher.blocksFrame = ADblocksFrame() else : AutoDispatcher.blocksFrame.show() # define what Locations button does when clicked def whenLocationsClicked(self,event) : if AutoDispatcher.locationsFrame == None : AutoDispatcher.locationsFrame = ADlocationsFrame() else : AutoDispatcher.locationsFrame.show() # define what Preferences button does when clicked def whenPreferencesClicked(self,event) : if AutoDispatcher.preferencesFrame == None : AutoDispatcher.preferencesFrame = ADpreferencesFrame() else : AutoDispatcher.preferencesFrame.show() # define what Save Settings button does when clicked def whenSaveSettingsClicked(self,event) : # Save to disk AutoDispatcher.instance.saveSettings() return # define what Sound List button does when clicked def whenSoundListClicked(self,event) : if AutoDispatcher.soundListFrame == None : AutoDispatcher.soundListFrame = ADsoundListFrame() # define what Sound Default button does when clicked def whenSoundDefaultClicked(self,event) : if AutoDispatcher.soundDefaultFrame == None : AutoDispatcher.soundDefaultFrame = ADsoundDefaultFrame() # Operations buttons # define what Start button does when clicked def whenStartClicked(self,event) : # leave the button off ADmainMenu.startButton.enabled = False AutoDispatcher.instance.start() # define what Stop button does when clicked def whenStopClicked(self,event) : AutoDispatcher.loop = False # leave the button off ADmainMenu.stopButton.enabled = False AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Stopping trains. Please wait!") # define what Locomotives button does when clicked def whenLocosClicked(self,event) : if AutoDispatcher.locosFrame == None : AutoDispatcher.locosFrame = ADlocosFrame() else : AutoDispatcher.locosFrame.show() # define what Trains button does when clicked def whenTrainsClicked(self,event) : if AutoDispatcher.trainsFrame == None : AutoDispatcher.trainsFrame = ADtrainsFrame() else : AutoDispatcher.trainsFrame.show() # define what Save Trains button does when clicked def whenSaveTrainsClicked(self,event) : AutoDispatcher.instance.saveTrains() return # Emergency buttons # define what Pause button does when clicked def whenPauseClicked(self,event) : if ADsettings.pauseMode != ADsettings.IGNORE : AutoDispatcher.instance.stopAll() AutoDispatcher.log("Script paused!") # define what Resume button does when clicked def whenResumeClicked(self,event) : if (ADsettings.pauseMode == ADsettings.IGNORE or not AutoDispatcher.paused) : return AutoDispatcher.instance.resume() AutoDispatcher.log("Script resumed!") # Simulation buttons # define what Step button does when clicked def whenStepClicked(self,event) : AutoDispatcher.instance.oneStep() def enableButtons(self, on) : if not AutoDispatcher.error : ADmainMenu.startButton.enabled = on ADmainMenu.stopButton.enabled = not on if ADsettings.pauseMode != ADsettings.IGNORE : ADmainMenu.pauseButton.enabled = not on # Enable/disable simulation buttons if AutoDispatcher.simulation : self.stepButton.enabled = not on ADmainMenu.autoButton.enabled = not on self.directionButton.enabled = True self.panelButton.enabled = True self.speedsButton.enabled = True self.indicationsButton.enabled = True self.signalTypesButton.enabled = True self.signalMastsButton.enabled = True self.sectionsButton.enabled = True self.blocksButton.enabled = True self.locationsButton.enabled = True self.preferencesButton.enabled = True self.soundListButton.enabled = True self.soundDefaultButton.enabled = True ADmainMenu.saveSettingsButton.enabled = ( AutoDispatcher.preferencesDirty and on) ADmainMenu.saveTrainsButton.enabled = (AutoDispatcher.trainsDirty and on) self.locoButton.enabled = True self.trainsButton.enabled = True AdFrame.enableApply(on) # Main window closure handler ================= class ADcloseWindow(WindowAdapter) : # define what window closure does # (overrides empty method of WindowAdapter) def windowClosing(self,event) : # Close window event.getWindow().dispose() # Then stop everything, # otherwise the script will continue running # until JMRI exits and user will be unable to # stop it! # Close all other windows AdFrame.disposeAll() # Make sure ADpowerMonitor listener is removed if AutoDispatcher.powerMonitor != None : AutoDispatcher.powerMonitor.dispose() # Stop background handler (it will take care of stopping other # threads, etc.) if AutoDispatcher.loop : AutoDispatcher.exiting = True AutoDispatcher.loop = False else : # If background handler si not running, allow user to save data AutoDispatcher.instance.saveBeforeExit() # Our Abstract frame class ================= class AdFrame (JmriJFrame) : framesList = [] applyEnabled = True def enableApply(on) : AdFrame.applyEnabled = on for f in AdFrame.framesList : f.applyButton.enabled = on enableApply = ADstaticMethod(enableApply) def disposeAll() : while len(AdFrame.framesList) > 0 : AdFrame.framesList[0].dispose() disposeAll = ADstaticMethod(disposeAll) def __init__(self, title) : # super.init JmriJFrame.__init__(self, title) self.setDefaultCloseOperation(JmriJFrame.HIDE_ON_CLOSE) self.contentPane.setLayout(BoxLayout(self.contentPane, BoxLayout.Y_AXIS)) self.contentPane.setBorder(ADmainMenu.spacing) AdFrame.framesList.append(self) self.cancelButton = JButton("Cancel") self.applyButton = JButton("Apply") self.applyButton.enabled = AdFrame.applyEnabled def dispose(self) : JmriJFrame.dispose(self) AdFrame.framesList.remove(self) # Direction window ================= class ADdirectionFrame (AdFrame) : directionsSwing = JComboBox(["CCW-CW", "EAST-WEST", "NORTH-SOUTH", "LEFT-RIGHT", "UP-DOWN"]) selectedDirectionNames = "" def __init__(self) : # Create and display Direction window # super.init AdFrame.__init__(self, "Direction") temppane = JPanel() temppane.setLayout(BoxLayout(temppane, BoxLayout.Y_AXIS)) temppane1 = JPanel() temppane1.setLayout(GridLayout(5, 1)) temppane1.add(AutoDispatcher.centerLabel( " The script runs trains in two directions. ")) temppane1.add(AutoDispatcher.centerLabel( " Directions can be assigned a pair of names of your choice ")) temppane1.add(AutoDispatcher.centerLabel( " i.e. CCW and CW, or EAST and WEST or NORTH and SOUTH, etc. ")) temppane1.add(JLabel("")) temppane.add(temppane1) temppane1 = JPanel() temppane1.setLayout(GridLayout(1, 2)) temppane1.add(JLabel(" Choose direction names:")) ADdirectionFrame.directionsSwing.setSelectedItem( ADsettings.directionNames[0] + "-" + ADsettings.directionNames[1]) self.comboListener = ADcomboListener() ADdirectionFrame.directionsSwing.addActionListener(self.comboListener) temppane1.add(ADdirectionFrame.directionsSwing) temppane.add(temppane1) self.directionQuestion = AutoDispatcher.centerLabel("") self.displayDirectionQuestion() temppane1 = JPanel() temppane1.setLayout(GridLayout(4, 1)) temppane1.add(AutoDispatcher.centerLabel( " In order to allow the correct identification of ")) temppane1.add(AutoDispatcher.centerLabel( " directions, choose the start and end sections ")) temppane1.add(self.directionQuestion) temppane1.add(JLabel("")) temppane.add(temppane1) temppane1 = JPanel() temppane1.setLayout(GridLayout(1, 2)) temppane1.add(JLabel(" Start section:")) self.ccwStartSwing = JTextField(ADsettings.ccwStart, 6) temppane1.add(self.ccwStartSwing) temppane1.add(JLabel(" End section:")) self.ccwEndSwing = JTextField(ADsettings.ccwEnd, 6) temppane1.add(self.ccwEndSwing) temppane.add(temppane1) self.contentPane.add(temppane) # Buttons* temppane = JPanel() temppane.setLayout(BoxLayout(temppane, BoxLayout.X_AXIS)) # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked temppane.add(self.cancelButton) # Apply button self.applyButton.actionPerformed = self.whenApplyClicked temppane.add(self.applyButton) self.contentPane.add(temppane) # Display frame self.pack() self.show() # routine to display direction question def displayDirectionQuestion(self) : ADdirectionFrame.selectedDirectionNames = ( ADdirectionFrame.directionsSwing.getSelectedItem()) selected = ADdirectionFrame.selectedDirectionNames[ :ADdirectionFrame.selectedDirectionNames.find("-")] self.directionQuestion.setText(" of a short \"" + selected + "\" bound route:") # Buttons of Direction window ================= # define what Cancel button in Direction Window does when clicked def whenCancelClicked(self,event) : AdFrame.dispose(self) AutoDispatcher.directionFrame = None # define what Apply button in Direction Window does when clicked def whenApplyClicked(self,event) : selected = ADdirectionFrame.selectedDirectionNames[ :ADdirectionFrame.selectedDirectionNames.find("-")] if selected != ADsettings.directionNames[0] : ADsettings.directionNames = [selected, ADdirectionFrame.selectedDirectionNames[ ADdirectionFrame.selectedDirectionNames.find("-")+1:]] ADsettings.ccwStart = self.ccwStartSwing.text ADsettings.ccwEnd = self.ccwEndSwing.text # Recompute direction along sections completion = AutoDispatcher.instance.setDirections() self.ccwStartSwing.text = ADsettings.ccwStart self.ccwEndSwing.text = ADsettings.ccwEnd # Update train directions for t in ADtrain.getList() : t.updateSwing() # Force redisplay of other windows with appropriate direction names if AutoDispatcher.panelFrame != None : AutoDispatcher.panelFrame.setColorLabels() if AutoDispatcher.sectionsFrame != None : AutoDispatcher.sectionsFrame.reDisplay() if AutoDispatcher.blocksFrame != None : AutoDispatcher.blocksFrame.reDisplay() if AutoDispatcher.trainsFrame != None : AutoDispatcher.trainsFrame.reDisplay() AutoDispatcher.setPreferencesDirty() if completion : AutoDispatcher.instance.setSignals() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Direction changes applied") # Combo listener for the Direction frame ================= class ADcomboListener(ActionListener) : # Updates direction names in Directions frame def actionPerformed(self, event) : if (ADdirectionFrame.directionsSwing.getSelectedItem() == ADdirectionFrame.selectedDirectionNames) : return AutoDispatcher.directionFrame.displayDirectionQuestion() # Panel settings window ================= class ADpanelFrame (AdFrame) : def __init__(self) : # Create and display Panel window # super.init AdFrame.__init__(self, "Panel") temppane = JPanel() temppane.setLayout(BorderLayout()) temppane.setBorder(ADmainMenu.spacing) temppane1 = JPanel() temppane1.setLayout(BoxLayout(temppane1, BoxLayout.Y_AXIS)) self.colorGroup = ButtonGroup() self.standardColorButton = JRadioButton( "Keep block colors defined in Layout Editor") self.standardColorButton.selected = not ADsettings.useCustomColors self.standardColorButton.actionPerformed = ( self.whenStandardColorsClicked) self.colorGroup.add(self.standardColorButton); temppane1.add(self.standardColorButton) self.customColorButton = JRadioButton( "Use colors to show sections status") self.customColorButton.selected = ADsettings.useCustomColors self.customColorButton.actionPerformed = self.whenCustomColorsClicked self.colorGroup.add(self.customColorButton); temppane1.add(self.customColorButton) temppane.add(temppane1, BorderLayout.NORTH); temppane1 = JPanel() temppane1.setBorder(ADmainMenu.spacing) temppane2 = JPanel() temppane2.setBorder(ADmainMenu.blackline) temppane2.setLayout(GridLayout(len(ADsettings.colorTable)+1, 3)) temppane2.add(JLabel(" Section")) temppane2.add(AutoDispatcher.centerLabel("Color")) temppane3 = JPanel() temppane3.setLayout(GridLayout(1, 4)) temppane3.add(AutoDispatcher.centerLabel("R")) temppane3.add(AutoDispatcher.centerLabel("G")) temppane3.add(AutoDispatcher.centerLabel("B")) temppane3.add(JLabel("")) temppane2.add(temppane3) self.colorLabels = [] self.colorSwing = [] self.colorPanes = [] self.rgb = [] colorList = ADsettings.colors.keys() colorList.append("CUSTOM") self.colorListener = ADcolorListener() for i in range(len(ADsettings.colorTable)) : self.colorLabels.append(JLabel("")) temppane2.add(self.colorLabels[i]) self.colorSwing.append(JComboBox(colorList)) c = ADsettings.colorTable[i] isCustom = c.startswith("R:") if isCustom : self.colorSwing[i].setSelectedItem("CUSTOM") rgbValues = [int(c[2:5]), int(c[7:10]), int(c[12:])] else : self.colorSwing[i].setSelectedItem(c) rgbValues = [0,0,0] self.colorSwing[i].setActionCommand(str(i)) self.colorSwing[i].addActionListener(self.colorListener) self.colorSwing[i].enabled = ADsettings.useCustomColors temppane2.add(self.colorSwing[i]) colorPane = JPanel() colorPane.setBorder(ADmainMenu.blackline) colorPane.setBackground(ADsettings.sectionColor[i]) self.colorPanes.append(colorPane) rgbItem = [] rgbPane = JPanel() rgbPane.setLayout(GridLayout(1, 4)) rgbListener = ADrgbListener(i) for j in range(3) : rgbItem.append(JSpinner(SpinnerNumberModel(rgbValues[j],0,255,1))) tf = rgbItem[j].getEditor().getTextField() tf.setColumns(2) rgbItem[j].setEnabled(isCustom) rgbItem[j].addChangeListener(rgbListener) rgbPane.add(rgbItem[j]) self.rgb.append(rgbItem) rgbPane.add(self.colorPanes[i]) temppane2.add(rgbPane) self.setColorLabels() temppane1.add(temppane2) temppane.add(temppane1, BorderLayout.CENTER); temppane1 = JPanel() temppane1.setLayout(BoxLayout(temppane1, BoxLayout.Y_AXIS)) self.widthGroup = ButtonGroup() self.standardWidthButton = JRadioButton( "Keep track width defined in Layout Editor") self.standardWidthButton.selected = not ADsettings.useCustomWidth self.widthGroup.add(self.standardWidthButton); temppane1.add(self.standardWidthButton) self.customWidthButton = JRadioButton( "Use track width to show blocks occupancy") self.customWidthButton.selected = ADsettings.useCustomWidth self.widthGroup.add(self.customWidthButton); temppane1.add(self.customWidthButton) temppane.add(temppane1, BorderLayout.SOUTH); self.contentPane.add(temppane) # Buttons* temppane = JPanel() temppane.setLayout(BoxLayout(temppane, BoxLayout.X_AXIS)) # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked temppane.add(self.cancelButton) # Apply button self.applyButton.actionPerformed = self.whenApplyClicked temppane.add(self.applyButton) self.contentPane.add(temppane) # Display frame self.pack() self.show() # routine to display color labels def setColorLabels(self) : sectionType = (" empty", " in manual mode", " occupied by rolling stock ", " allocated to " + ADsettings.directionNames[0] + " train", " allocated to " + ADsettings.directionNames[1] + " train", " occupied by " + ADsettings.directionNames[0] + " train", " occupied by " + ADsettings.directionNames[1] + " train") for i in range(len(self.colorLabels)) : self.colorLabels[i].setText(sectionType[i]) # Get input RGB values and convert them to string def getRGBinput(self,i) : c = [] for j in range(3) : c.append(self.rgb[i][j].getValue()) return ADsettings.rgbToString(c) # Buttons of Panel window ================= # define what Standard Colors button in Panel Window does when clicked def whenStandardColorsClicked(self,event) : for i in range(len(self.colorLabels)) : self.colorSwing[i].enabled = False AutoDispatcher.setPreferencesDirty() # define what Custom Colors button in Panel Window does when clicked def whenCustomColorsClicked(self,event) : for i in range(len(self.colorLabels)) : self.colorSwing[i].enabled = True AutoDispatcher.setPreferencesDirty() # define what Cancel button in Direction Window does when clicked def whenCancelClicked(self,event) : AdFrame.dispose(self) AutoDispatcher.panelFrame = None # define what Apply button in Panel Window does when clicked def whenApplyClicked(self,event) : for i in range(len(self.colorLabels)) : c = self.colorSwing[i].getSelectedItem() if c == "CUSTOM" : c = AutoDispatcher.panelFrame.getRGBinput(i) ADsettings.colorTable[i] = c ADsettings.initColors() ADsettings.useCustomColors = self.customColorButton.selected ADsettings.useCustomWidth = self.customWidthButton.selected AutoDispatcher.setPreferencesDirty() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Panel changes applied") # Listener for the color ComboBox ================= class ADcolorListener(ActionListener) : def actionPerformed(self, event) : i = int(event.getActionCommand()) c = AutoDispatcher.panelFrame.colorSwing[i].getSelectedItem() isCustom = c == "CUSTOM" for j in range(3) : AutoDispatcher.panelFrame.rgb[i][j].setEnabled(isCustom) if isCustom : c = AutoDispatcher.panelFrame.getRGBinput(i) AutoDispatcher.panelFrame.colorPanes[i].setBackground(ADsettings.stringToColor(c)) # Listener for the rgb text fields ================= class ADrgbListener(ChangeListener) : def __init__(self, ind) : self.i = ind def stateChanged(self, event) : c = AutoDispatcher.panelFrame.colorSwing[self.i].getSelectedItem() if c != "CUSTOM" : return c = AutoDispatcher.panelFrame.getRGBinput(self.i) p = AutoDispatcher.panelFrame.colorPanes[self.i] p.setBackground(ADsettings.stringToColor(c)) # Our Abstract frame with a scroll pane ================= class AdScrollFrame (AdFrame) : # Subclasses must define methods: # self.createHeader() (output returned in self.header JPanel) # self.createDetail() (output returned in self.detail JPanel) # self.createButtons() (output returned in self.buttons JPanel) def __init__(self, title, firstLine) : # super.init AdFrame.__init__(self, title) # Create first Line if firstLine != None : temppane = JPanel() temppane.setLayout(BoxLayout(temppane, BoxLayout.X_AXIS)) temppane.add(firstLine) self.contentPane.add(temppane) # Create scroll pane self.scrollPane = JScrollPane() self.firstTime = True self.createScroll() self.contentPane.add(self.scrollPane) # Create buttons at bottom self.buttons = JPanel() self.buttons.setLayout(BoxLayout(self.buttons, BoxLayout.X_AXIS)) self.createButtons() self.contentPane.add(self.buttons) # Adjust size self.adjustSize() # Display frame self.show() def createScroll(self) : # Create header and scroll pane contents self.header = JPanel() self.createHeader() self.detail = JPanel() self.createDetail() self.scrollSize = self.detail.getPreferredSize() if self.header != None : #Get panel size headerSize = self.header.getPreferredSize() # Make sure header and scrollarea have the same width if headerSize.width < self.scrollSize.width : headerSize.width = self.scrollSize.width self.header.setPreferredSize(headerSize) elif self.scrollSize.width < headerSize.width : self.scrollSize.width = headerSize.width self.detail.setPreferredSize(self.scrollSize) self.scrollPane.setColumnHeaderView(self.header) self.scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER, JPanel()) self.scrollPane.setViewportView(self.detail) # Adjust size (when updating window - adding or removing speeds) frameSize = self.getPreferredSize() if self.header != None : frameSize.height = self.scrollSize.height + 110 else : frameSize.height = self.scrollSize.height + 95 self.setSize(frameSize) self.firstTime = False def reDisplay(self) : self.createScroll() self.adjustSize() def adjustSize(self) : self.pack() frameSize = self.getPreferredSize() frameSize.width = self.scrollSize.width + 30 if frameSize.width > AutoDispatcher.screenSize.width : frameSize.width = AutoDispatcher.screenSize.width self.setSize(frameSize) # Speeds window ================= class ADspeedsFrame (AdScrollFrame) : def __init__(self) : # Create and display Speeds window # super.init AdScrollFrame.__init__(self, "Speed Levels", AutoDispatcher.centerLabel( "List of supported speed levels (minimum speed listed first)")) def createHeader(self): # Fill contents of Header self.header.setLayout(GridLayout(1, 2)) self.header.add(AutoDispatcher.centerLabel("Name")) self.header.add(JLabel("")) def createDetail(self): # Fill contents of scroll area self.detail.setLayout(GridLayout(len(ADsettings.speedsList), 2)) if self.firstTime : self.speedNamesSwing = [] self.speedDefaultSwing = [] self.speedGroup = ButtonGroup() ind = 0 for s in ADsettings.speedsList : if self.firstTime : self.speedNamesSwing.append(JTextField(s, 20)) self.detail.add(self.speedNamesSwing[ind]) if ind == 0 : self.detail.add(JLabel("")) else : deleteButton = JButton("Delete") deleteButton.setActionCommand(str(ind)) deleteButton.actionPerformed = self.whenDeleteClicked self.detail.add(deleteButton) ind += 1 def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Add button self.addButton = JButton("Add") self.addButton.actionPerformed = self.whenAddClicked self.buttons.add(self.addButton) # Apply button self.applyButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.applyButton) # Buttons of Speeds window ================= # define what Cancel button in Speeds Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.speedsFrame = None # define what Add button in Speeds Window does when clicked def whenAddClicked(self,event) : ADsettings.speedsList.append("") self.speedNamesSwing.append(JTextField("", 20)) radioButton = JRadioButton("Default speed") self.speedDefaultSwing.append(radioButton) self.speedGroup.add(radioButton) self.speedsChanged() # define what Delete button in Speeds Window does when clicked def whenDeleteClicked(self,event) : ind = int(event.getActionCommand()) if (JOptionPane.showConfirmDialog(None, "Remove speed level \"" + ADsettings.speedsList[ind] + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) : return ADsettings.speedsList.pop(ind) self.speedsChanged() # define what Apply button in Speeds Window does when clicked def whenApplyClicked(self,event) : ind = 0 for s in ADsettings.speedsList : ADsettings.speedsList[ind] = self.speedNamesSwing[ind].text ind += 1 self.speedsChanged() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Speed names changed") def speedsChanged(self) : if AutoDispatcher.blocksFrame != None : AutoDispatcher.blocksFrame.reDisplay() if AutoDispatcher.indicationsFrame != None : AutoDispatcher.indicationsFrame.reDisplay() if AutoDispatcher.signalEditFrame != None : AutoDispatcher.signalEditFrame.reDisplay() if AutoDispatcher.locosFrame != None : AutoDispatcher.locosFrame.reDisplay() if AutoDispatcher.trainDetailFrame != None : AutoDispatcher.trainDetailFrame.reDisplay() AutoDispatcher.setPreferencesDirty() self.reDisplay() # Indications window ================= class ADindicationsFrame (AdScrollFrame) : def __init__(self) : # Create and display Speeds window # super.init AdScrollFrame.__init__(self, "Signal Indications", AutoDispatcher.centerLabel("List of supported signal indications")) def createHeader(self): # Fill contents of Header self.header.setLayout(GridLayout(1, 5)) header1 = JPanel() header1.setLayout(GridLayout(1, 2)) self.header.add(AutoDispatcher.centerLabel("Indication")) header1.add(AutoDispatcher.centerLabel("Next section")) header1.add(AutoDispatcher.centerLabel("Turnouts ahead")) self.header.add(header1) self.header.add(AutoDispatcher.centerLabel("Next signal indication")) self.header.add(AutoDispatcher.centerLabel("Speed")) self.header.add(JLabel("")) def createDetail(self): # Fill contents of scroll area indicationNames = ["-"] for a in ADsettings.indicationsList : indicationNames.append(a.name) speedNames = [] for s in ADsettings.speedsList : speedNames.append(s) maxSpeeds = len(speedNames) self.detail.setLayout(GridLayout(len(ADsettings.indicationsList), 5)) ind = 0 for a in ADsettings.indicationsList : self.detail.add(a.nameSwing) temppane1 = JPanel() temppane1.setLayout(GridLayout(1, 2)) if ind == 0 : temppane1.add(AutoDispatcher.centerLabel("Occupied")) temppane1.add(AutoDispatcher.centerLabel("-")) self.detail.add(temppane1) self.detail.add(AutoDispatcher.centerLabel("-")) self.detail.add(AutoDispatcher.centerLabel("Stop")) self.detail.add(JLabel("")) elif ind == 1 : temppane1.add(AutoDispatcher.centerLabel("Available")) temppane1.add(AutoDispatcher.centerLabel("-")) self.detail.add(temppane1) self.detail.add(AutoDispatcher.centerLabel("-")) a.speedSwing = JComboBox(speedNames) if a.speed > maxSpeeds : a.speedSwing.setSelectedIndex(maxSpeeds-1) else : a.speedSwing.setSelectedIndex(a.speed-1) self.detail.add(a.speedSwing) self.detail.add(JLabel("")) else : temppane1.add(AutoDispatcher.centerLabel("Available")) temppane1.add(a.nextTurnoutSwing) self.detail.add(temppane1) a.nextIndicationSwing = JComboBox(indicationNames) a.nextIndicationSwing.setSelectedIndex(a.nextIndication+1) self.detail.add(a.nextIndicationSwing) a.speedSwing = JComboBox(speedNames) if a.speed > maxSpeeds : a.speedSwing.setSelectedIndex(maxSpeeds-1) else : a.speedSwing.setSelectedIndex(a.speed-1) self.detail.add(a.speedSwing) deleteButton = JButton("Delete") deleteButton.setActionCommand(str(ind)) deleteButton.actionPerformed = self.whenDeleteIndicationClicked self.detail.add(deleteButton) ind += 1 def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Add button self.addButton = JButton("Add") self.addButton.actionPerformed = self.whenAddClicked self.buttons.add(self.addButton) # Apply button self.applyButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.applyButton) # Buttons of Indications window ================= # define what Cancel button in Indications Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.indicationsFrame = None # define what Add button in Indications Window does when clicked def whenAddClicked(self,event) : ADsettings.indicationsList.append(ADindication("New indication", -1, -1, len(ADsettings.speedsList))) ADsignalType.adjust() self.indicationsChanged() # define what Delete button in Indications Window does when clicked def whenDeleteIndicationClicked(self,event) : ind = int(event.getActionCommand()) if (JOptionPane.showConfirmDialog(None, "Remove signal indication \"" + ADsettings.indicationsList[ind].name + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) : return ADsettings.indicationsList.pop(ind) for a in ADsettings.indicationsList : if a.nextIndication == ind : a.nextIndication = -1 elif a.nextIndication > ind : a.nextIndication -=1 AutoDispatcher.setPreferencesDirty() self.indicationsChanged() # define what Apply button in Indications Window does when clicked def whenApplyClicked(self,event) : ind = 0 for a in ADsettings.indicationsList : a.name = a.nameSwing.text if ind > 0 : a.speed = a.speedSwing.getSelectedIndex() + 1 if ind > 1 : a.nextIndication = a.nextIndicationSwing.getSelectedIndex()-1 a.nextTurnout = a.nextTurnoutSwing.getSelectedIndex()-1 ind += 1 self.indicationsChanged() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Signal indications changed") def indicationsChanged(self) : if AutoDispatcher.signalEditFrame != None : AutoDispatcher.signalEditFrame.reDisplay() AutoDispatcher.setPreferencesDirty() self.reDisplay() # Signal Types window ================= class ADsignalTypesFrame (AdScrollFrame) : def __init__(self) : # Create and display Speeds window # super.init AdScrollFrame.__init__(self, "Signal Types", AutoDispatcher.centerLabel("List of supported signal types")) def createHeader(self): # Fill contents of Header self.header = None def createDetail(self): # Fill contents of scroll area self.detail.setLayout(GridLayout(len(ADsettings.signalTypes), 2)) ind = 0 for s in ADsettings.signalTypes : self.detail.add(JLabel(s.name)) temppane1 = JPanel() temppane1.setLayout(GridLayout(1, 4)) if s.headsNumber == 1 : temppane1.add(AutoDispatcher.centerLabel("1 Head")) else : temppane1.add(AutoDispatcher.centerLabel(str(s.headsNumber) + " Heads")) editButton = JButton("Edit") editButton.setActionCommand(str(ind)) editButton.actionPerformed = self.whenEditClicked temppane1.add(editButton) duplicateButton = JButton("Duplicate") duplicateButton.setActionCommand(str(ind)) duplicateButton.actionPerformed = self.whenDuplicateClicked temppane1.add(duplicateButton) if ind == 0 : temppane1.add(JLabel("")) else : if s.inUse > 0 : temppane1.add(AutoDispatcher.centerLabel("In use")) else : deleteButton = JButton("Delete") deleteButton.setActionCommand(str(ind)) deleteButton.actionPerformed = self.whenDeleteClicked temppane1.add(deleteButton) self.detail.add(temppane1) ind += 1 def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Add button self.addButton = JButton("Add") self.addButton.actionPerformed = self.whenAddClicked self.buttons.add(self.addButton) # Buttons of Signal Types window ================= # define what Cancel button in Signal Types Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.signalTypesFrame = None if AutoDispatcher.signalEditFrame != None : AutoDispatcher.signalEditFrame.dispose() AutoDispatcher.signalEditFrame = None # define what Edit button in Signal Types Window does when clicked def whenEditClicked(self,event) : ind = int(event.getActionCommand()) if AutoDispatcher.signalEditFrame != None : AutoDispatcher.signalEditFrame.show() if (ADsettings.signalTypes[ind] == AutoDispatcher.signalEditFrame.editSignal) : return if (JOptionPane.showConfirmDialog(None, "Save changes to signal type \"" + AutoDispatcher.signalEditFrame.editSignal.name + "\" before editing signal type \"" + ADsettings.signalTypes[ind].name + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) != 1) : AutoDispatcher.signalEditFrame.whenApplyClicked(None) AutoDispatcher.signalEditFrame.dispose() AutoDispatcher.signalEditFrame = ( ADsignalEditFrame(ADsettings.signalTypes[ind])) # define what Add button in Signal Types Window does when clicked def whenAddClicked(self,event) : ADsettings.signalTypes.append(ADsignalType( "New signal type", [], [])) self.signalTypesChanged() # define what Delete button in Signal Types Window does when clicked def whenDeleteClicked(self,event) : ind = int(event.getActionCommand()) if (JOptionPane.showConfirmDialog(None, "Remove signal type \"" + ADsettings.signalTypes[ind].name + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) : return ADsettings.signalTypes.pop(ind) AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Signal type \"" + ADsettings.signalTypes[ind].name + " removed") self.signalTypesChanged() # define what Duplicate button in Signal Types Window does when clicked def whenDuplicateClicked(self,event) : ind = int(event.getActionCommand()) ADsettings.signalTypes.append( ADsignalType(ADsettings.signalTypes[ind].name + " copy", ADsettings.signalTypes[ind].aspects, ADsettings.signalTypes[ind].speeds)) AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Signal type \"" + ADsettings.signalTypes[ind].name + " duplicated") self.signalTypesChanged() def signalTypesChanged(self) : if AutoDispatcher.signalMastsFrame != None : AutoDispatcher.signalMastsFrame.reDisplay() AutoDispatcher.setPreferencesDirty() self.reDisplay() # Signal Edit Window ================= class ADsignalEditFrame (AdScrollFrame) : def __init__(self, signal) : # Create and display Speeds window self.editSignal = signal first = JPanel() first.setLayout(BoxLayout(first, BoxLayout.X_AXIS)) first.add(AutoDispatcher.centerLabel("Name: ")) first.add(self.editSignal.nameSwing) first.add(AutoDispatcher.centerLabel("Heads: ")) self.headsSwing = JComboBox(["1", "2", "3", "4", "5"]) self.headsSwing.setSelectedIndex(self.editSignal.headsNumber -1) if self.editSignal == ADsettings.signalTypes[0] : self.headsSwing.enabled = False first.add(self.headsSwing) # super.init AdScrollFrame.__init__(self, "Edit signal type", first) def createHeader(self): # Fill contents of Header self.header.setLayout(GridLayout(1, 3 + self.editSignal.headsNumber)) self.header.add(AutoDispatcher.centerLabel("Signal indication")) for i in range(self.editSignal.headsNumber) : self.header.add(AutoDispatcher.centerLabel("Head" + str(i+1))) self.header.add(AutoDispatcher.centerLabel("Speed")) self.header.add(AutoDispatcher.centerLabel("Override speed")) def createDetail(self): # Fill contents of scroll area self.detail.setLayout(GridLayout(self.editSignal.aspectsNumber, 3 + self.editSignal.headsNumber)) speedNames = ["No"] for s in ADsettings.speedsList : speedNames.append(s) self.signalSpeedSwing = [] self.signalAspectsSwing = [] ind = 0 headsAspects = AutoDispatcher.headsAspects.keys() headsAspects.sort() for a in self.editSignal.aspects : if ind >= len(ADsettings.indicationsList) : # self.detail.add(JLabel("Not defined")) break # else : self.detail.add(JLabel(ADsettings.indicationsList[ind].name)) aspectLine = [] for aa in a : aspectSwing = JComboBox(headsAspects) aspectSwing.setSelectedItem(AutoDispatcher.inverseAspects[aa]) aspectLine.append(aspectSwing) self.detail.add(aspectSwing) self.signalAspectsSwing.append(aspectLine) self.detail.add(AutoDispatcher.centerLabel( ADsettings.getSpeedName(ADsettings.indicationsList[ind].speed))) speedSwing = JComboBox(speedNames) speedIndex = self.editSignal.speeds[ind]+1 if speedIndex >= len(speedNames) : speedIndex = len(speedNames) -1 speedSwing.setSelectedIndex(speedIndex) if ind == 0 : speedSwing.enabled = False self.signalSpeedSwing.append(speedSwing) self.detail.add(speedSwing) ind += 1 def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Apply button self.applyButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.applyButton) # Buttons of Signal Edit window ================= # define what Cancel button in Signal Edit Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.signalEditFrame = None # define what Apply button in Signal Edit Window does when clicked def whenApplyClicked(self,event) : if self.editSignal != None : self.editSignal.name = self.editSignal.nameSwing.text for i in range(self.editSignal.aspectsNumber) : for j in range(self.editSignal.headsNumber) : self.editSignal.aspects[i][j] = AutoDispatcher.headsAspects[ self.signalAspectsSwing[i][j].getSelectedItem()] self.editSignal.speeds[i] = self.signalSpeedSwing[ i].getSelectedIndex()-1 newHeads = self.headsSwing.getSelectedIndex() + 1 if newHeads != self.editSignal.headsNumber : diff = newHeads - self.editSignal.headsNumber if diff > 0 : for i in range(diff) : self.editSignal.aspects[0].append(SignalHead.RED) for i in range(len(self.editSignal.aspects) -1) : for j in range(diff) : self.editSignal.aspects[i+1].append( SignalHead.GREEN) else : for a in self.editSignal.aspects : for i in range(-diff) : a.pop() self.editSignal.headsNumber = ( self.headsSwing.getSelectedIndex() + 1) if AutoDispatcher.signalTypesFrame != None : AutoDispatcher.signalTypesFrame.reDisplay() if AutoDispatcher.signalMastsFrame != None : AutoDispatcher.signalMastsFrame.reDisplay() AutoDispatcher.setPreferencesDirty() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Signal type \"" + self.editSignal.name + " modified") self.reDisplay() # Signal Types window ================= class ADsignalMastsFrame (AdScrollFrame) : def __init__(self) : # Create and display Speeds window # super.init AdScrollFrame.__init__(self, "Signal masts", None) def createHeader(self): # Fill contents of Header if self.firstTime : names = ADsignalMast.getNames() names.sort() self.signals = [] self.maxHeads = 1 for name in names : s = ADsignalMast.getByName(name) # Shouldn't happen if s != None : if s.headsNumber > self.maxHeads : self.maxHeads = s.headsNumber self.signals.append(s) self.header.setLayout(GridLayout(1, 3)) self.header.add(AutoDispatcher.centerLabel("Signal name")) self.header.add(AutoDispatcher.centerLabel("Signal type")) header1 = JPanel() header1.setLayout(GridLayout(1, self.maxHeads + 1)) for i in range(self.maxHeads) : header1.add(AutoDispatcher.centerLabel("Head" + str(i+1))) header1.add(JLabel("")) self.header.add(header1) def createDetail(self): # Fill contents of scroll area self.detail.setLayout(GridLayout(len(self.signals), 3)) self.namesSwing = [] self.typesSwing = [] self.headsSwing = [] types = [] for t in ADsettings.signalTypes : types.append(t.name) ind = 0 for s in self.signals : nameSwing = JTextField(s.name, 20) self.detail.add(nameSwing) self.namesSwing.append(nameSwing) typeSwing = JComboBox(types) typeSwing.setSelectedItem(s.signalType.name) self.detail.add(typeSwing) self.typesSwing.append(typeSwing) temppane1 = JPanel() temppane1.setLayout(GridLayout(1, self.maxHeads)) i = 0 headsLineSwing = [] for h in s.signalHeads : headSwing = JComboBox(AutoDispatcher.signalHeadNames) if i < self.maxHeads : if h != None : headSwing.setSelectedItem(h.name) temppane1.add(headSwing) headsLineSwing.append(headSwing) i += 1 self.headsSwing.append(headsLineSwing) while i < self.maxHeads : temppane1.add(JLabel("")) i += 1 if s.inUse > 0 : temppane1.add(AutoDispatcher.centerLabel("In use")) else : deleteButton = JButton("Delete") deleteButton.setActionCommand(str(ind)) deleteButton.actionPerformed = self.whenDeleteClicked temppane1.add(deleteButton) self.detail.add(temppane1) ind += 1 def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Add button self.addButton = JButton("Add") self.addButton.actionPerformed = self.whenAddClicked self.buttons.add(self.addButton) # Apply button self.applyButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.applyButton) # Buttons of Signal Types window ================= # define what Cancel button in Signal Masts Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.signalMastsFrame = None # define what Add button in Signal Masts Window does when clicked def whenAddClicked(self,event) : newName = "New signal" i = 1 while ADsignalMast.signalsList.has_key(newName) : i += 1 newName = "New signal " + str(i) self.signals.append(ADsignalMast.provideSignal(newName)) self.reDisplay() # define what Apply button in Signal Masts Window does when clicked def whenApplyClicked(self,event) : ind = 0 for s in self.signals : newName = self.namesSwing[ind].text if newName != s.name : if ADsignalMast.signalsList.has_key(newName) : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Duplicate signal masts name \"" + newName + "\" - ignored") self.namesSwing[ind].text = s.name else : s.name = newName typeName = self.typesSwing[ind].getSelectedItem() newType = s.signalType for t in ADsettings.signalTypes : if t.name == typeName : newType = t break if newType != s.signalType : s.signalType.changeUse(-1) while newType.headsNumber > s.headsNumber : s.signalHeads.append(None) s.headsNumber += 1 s.signalType = newType s.signalType.changeUse(1) if newType.headsNumber > self.maxHeads : self.maxHeads = newType.headsNumber i = 0 for h in self.headsSwing[ind] : headName = h.getSelectedItem() if headName == "" : s.signalHeads[i] = None else : s.signalHeads[i] = ADsignalHead(headName) i += 1 ind += 1 newDic = {} for s in ADsignalMast.getList() : newDic[s.name] = s ADsignalMast.signalsList = newDic AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Signal masts updated") self.signalMastsChanged() # define what Delete button in Signal Masts Window does when clicked def whenDeleteClicked(self,event) : ind = int(event.getActionCommand()) name = self.signals[ind].name if (JOptionPane.showConfirmDialog(None, "Remove signal mast \"" + name + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) : return removed = self.signals.pop(ind) removed.changeUse(-1) AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Signal mast \"" + name + " removed") self.signalMastsChanged() def signalMastsChanged(self) : if AutoDispatcher.signalTypesFrame != None : AutoDispatcher.signalTypesFrame.reDisplay() if AutoDispatcher.sectionsFrame != None : AutoDispatcher.sectionsFrame.reDisplay() AutoDispatcher.setPreferencesDirty() self.reDisplay() # Sections Window ================= class ADsectionsFrame (AdScrollFrame) : def __init__(self) : # Create Sections window # super.init AdScrollFrame.__init__(self, "Sections", None) def createHeader(self): # Fill contents of Header self.header.setLayout(GridLayout(1, 8)) self.header.add(AutoDispatcher.centerLabel("Section")) self.header.add(AutoDispatcher.centerLabel("One-Way")) self.header.add(AutoDispatcher.centerLabel("Transit-Only")) self.header.add(AutoDispatcher.centerLabel( ADsettings.directionNames[0] + " signal")) self.header.add(AutoDispatcher.centerLabel( ADsettings.directionNames[0] + " stop at beginning")) self.header.add(AutoDispatcher.centerLabel( ADsettings.directionNames[1] + " signal")) self.header.add(AutoDispatcher.centerLabel( ADsettings.directionNames[1] + " stop at beginning")) self.header.add(AutoDispatcher.centerLabel("Man. sensor")) def createDetail(self): # Fill contents of scroll area sections = ADsection.getSectionsTable() self.detail.setLayout(GridLayout(len(sections), 6)) both = (ADsettings.directionNames[0] + "-" + ADsettings.directionNames[1]) signalMastList = ADsignalMast.getNames() signalMastList.sort() signalPopUp = [""] for s in signalMastList : signalPopUp.append("s: " + s) for s in AutoDispatcher.signalHeadNames : if not s in signalMastList and s.strip() != "" : signalPopUp.append("h: " + s) for s in sections : self.detail.add(AutoDispatcher.centerLabel(s[0])) ss = ADsection.getByName(s[0]) ss.oneWaySwing.removeAllItems() ss.oneWaySwing.addItem("") ss.oneWaySwing.addItem(ADsettings.directionNames[0]) ss.oneWaySwing.addItem(ADsettings.directionNames[1]) self.detail.add(ss.oneWaySwing) ss.oneWaySwing.setSelectedItem(s[1]) ss.transitOnlySwing.removeAllItems() ss.transitOnlySwing.addItem("") ss.transitOnlySwing.addItem(both) ss.transitOnlySwing.addItem(both + "+") ss.transitOnlySwing.addItem(ADsettings.directionNames[0]) ss.transitOnlySwing.addItem(ADsettings.directionNames[0] + "+") ss.transitOnlySwing.addItem(ADsettings.directionNames[1]) ss.transitOnlySwing.addItem(ADsettings.directionNames[1] + "+") self.detail.add(ss.transitOnlySwing) ss.transitOnlySwing.setSelectedItem(s[2]) j = ADsettings.ccw for k in range(2) : ss.signalSwing[j] = JComboBox(signalPopUp) self.detail.add(ss.signalSwing[j]) if ss.signal[j] != None : ss.signalSwing[j].setSelectedItem("s: " + ss.signal[j].name) temppane = JPanel() temppane.setLayout(GridLayout(1, 2)) if ss.stopAtBeginning[j] >= 0 : ss.stopAtBeginningSwing[j].selected = True ss.stopAtBeginningDelay[j].text = str(ss.stopAtBeginning[j]) else : ss.stopAtBeginningSwing[j].selected = False ss.stopAtBeginningDelay[j].text = "0.0" temppane.add(ss.stopAtBeginningSwing[j]) temppane.add(ss.stopAtBeginningDelay[j]) self.detail.add(temppane) j = 1-j ss.manualSensorSwing = JComboBox(ADsection.sensorNames) if s[6] in ADsection.sensorNames : ss.manualSensorSwing.setSelectedItem(s[6]) self.detail.add(ss.manualSensorSwing) def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Apply button self.applyButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.applyButton) # Buttons of Sections window ================= # define what Cancel button in Sections Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.sectionsFrame = None # define what Apply button in Sections Window does when clicked def whenApplyClicked(self,event) : for s in ADsection.getList() : s.updateFromSwing() if AutoDispatcher.signalMastsFrame != None : AutoDispatcher.signalMastsFrame.reDisplay() ADgridGroup.create() AutoDispatcher.setPreferencesDirty() self.reDisplay() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Sections changes applied") # Blocks Window ================= class ADblocksFrame (AdScrollFrame) : def __init__(self) : # Create Blocks window # super.init AdScrollFrame.__init__(self, "Blocks", None) def createHeader(self): # Fill contents of Header # Retrieve section names self.sectionNames = ADsection.getNames() self.sectionNames.sort() # Check if the direction of any section can be reversed self.columns = 10 self.canBeReversed = [] self.inversionPoints = [] for sectionName in self.sectionNames : section = ADsection.getByName(sectionName) canBe = False # Direction can be reversed only if all entry points # in one direction connect with sections in opposite direction # (i.e. the section is at the end of a reversing loop) invPoints = ["Inv. Points"] for direction in [False, True] : entries = section.getEntries(direction) if len(entries) > 0 : canBe1 = True invPoints1 = [] for entry in entries : if not entry.getDirectionChange() : canBe1 = False break invPoints1.append(entry.getExternalSection().getName()) if canBe1 : canBe = True invPoints.extend(invPoints1) # Keep track of the condition self.canBeReversed.append(canBe) if canBe : self.columns = 11 else : invPoints = [] self.inversionPoints.append(invPoints) self.header.setLayout(GridLayout(2, self.columns)) self.header.add(AutoDispatcher.centerLabel("Section")) self.header.add(AutoDispatcher.centerLabel("Block")) for i in range(2) : for j in range(2) : header1 = JPanel() header1.setLayout(GridLayout(1, 2)) for k in range(2) : header1.add(AutoDispatcher.centerLabel( ADsettings.directionNames[i])) self.header.add(header1) self.header.add(AutoDispatcher.centerLabel( ADsettings.directionNames[i])) self.header.add(AutoDispatcher.centerLabel( ADsettings.directionNames[i])) if self.columns > 10 : self.header.add(JLabel("")) self.header.add(JLabel("")) self.header.add(JLabel("")) for i in range(2) : header1 = JPanel() header1.setLayout(GridLayout(1, 2)) header1.add(AutoDispatcher.centerLabel("alloc.")) header1.add(AutoDispatcher.centerLabel("safe")) self.header.add(header1) header1 = JPanel() header1.setLayout(GridLayout(1, 2)) header1.add(AutoDispatcher.centerLabel("stop")) header1.add(AutoDispatcher.centerLabel("brake")) self.header.add(header1) self.header.add(AutoDispatcher.centerLabel("speed")) self.header.add(AutoDispatcher.centerLabel("action")) if self.columns > 10 : self.header.add(AutoDispatcher.centerLabel("Reverse")) def createDetail(self): # Fill contents of scroll area # Compute total number of lines linesNumber = len(self.sectionNames) * 2 ind = 0 for sectionName in self.sectionNames : s = ADsection.getByName(sectionName) lines = len(s.getBlocks(True)) if lines < len(self.inversionPoints[ind]) : lines = len(self.inversionPoints[ind]) linesNumber += lines ind +=1 self.detail.setLayout(GridLayout(linesNumber, self.columns)) # Prepare list for speeds ComboBox speeds = [""] speeds.extend(ADsettings.speedsList) for sectionName in self.sectionNames : canBe = self.canBeReversed.pop(0) invPoints = self.inversionPoints.pop(0) section = ADsection.getByName(sectionName) for block in section.getBlocks(ADsettings.ccw) : self.detail.add(AutoDispatcher.centerLabel(sectionName)) self.detail.add(AutoDispatcher.centerLabel(block.getName())) j = ADsettings.ccw for k in range(2) : temppane = JPanel() temppane.setLayout(GridLayout(1, 2)) block.allocationSwing[j].setSelected(block == ( section.allocationPoint[j])) temppane.add(block.allocationSwing[j]) block.safeSwing[j].setSelected(block == ( section.safePoint[j])) temppane.add(block.safeSwing[j]) self.detail.add(temppane) temppane = JPanel() temppane.setLayout(GridLayout(1, 2)) block.stopSwing[j].setSelected(block == ( section.stopBlock[j])) temppane.add(block.stopSwing[j]) block.brakeSwing[j].setSelected(block == ( section.brakeBlock[j])) temppane.add(block.brakeSwing[j]) self.detail.add(temppane) block.speedSwing[j] = JComboBox(speeds) block.speedSwing[j].setSelectedIndex(block.getSpeed(j)) self.detail.add(block.speedSwing[j]) block.actionSwing[j].text = block.action[j] self.detail.add(block.actionSwing[j]) j = 1 - j if self.columns > 10 : if canBe and sectionName != "" : reverseButton = JButton("Reverse") reverseButton.setActionCommand(sectionName) reverseButton.actionPerformed = self.whenReverseClicked self.detail.add(reverseButton) else : if len(invPoints) > 0 : self.detail.add(AutoDispatcher.centerLabel(invPoints.pop(0))) else : self.detail.add(JLabel("")) sectionName = "" # Add a "None" line self.detail.add(JLabel("")) self.detail.add(AutoDispatcher.centerLabel("None")) j = ADsettings.ccw for k in range(2) : temppane = JPanel() temppane.setLayout(GridLayout(1, 2)) temppane.add(JLabel("")) section.safeNoneSwing[j].setSelected( section.safePoint[j] == None) temppane.add(section.safeNoneSwing[j]) self.detail.add(temppane) temppane = JPanel() temppane.setLayout(GridLayout(1, 2)) temppane.add(JLabel("")) section.brakeNoneSwing[j].setSelected( section.brakeBlock[j] == None) temppane.add(section.brakeNoneSwing[j]) self.detail.add(temppane) self.detail.add(JLabel("")) self.detail.add(JLabel("")) j = 1 - j if self.columns > 10 : if len(invPoints) > 0 : self.detail.add(AutoDispatcher.centerLabel(invPoints.pop(0))) while len(invPoints) > 0 : for i in range(self.columns - 1) : self.detail.add(JLabel("")) self.detail.add(AutoDispatcher.centerLabel(invPoints.pop(0))) else : self.detail.add(JLabel("")) # Add an empty line between sections for i in range(self.columns) : self.detail.add(JLabel("")) def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Apply button self.applyButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.applyButton) # Buttons of Blocks window ================= # define what Cancel button in Blocks Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.blocksFrame = None # define what Reverse button in Blocks Window does when clicked def whenReverseClicked(self,event) : sectionName = event.getActionCommand() section = ADsection.getByName(sectionName) # Reverse blocks order section.manuallyFlip() # Adjust entry points AutoDispatcher.instance.findTransitPoints() self.reDisplay() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Direction of section " + sectionName + " reversed") return # define what Apply button in Blocks Window does when clicked def whenApplyClicked(self,event) : for section in ADsection.getList() : section.safePoint[0] = section.safePoint[1] = None for block in section.getBlocks(True) : for j in range(2) : if block.allocationSwing[j].isSelected() : section.allocationPoint[j] = block if block.safeSwing[j].isSelected() : section.safePoint[j] = block if block.stopSwing[j].isSelected() : section.stopBlock[j] = block if block.brakeSwing[j].isSelected() : section.brakeBlock[j] = block block.speed[j] = block.speedSwing[j].getSelectedIndex() block.action[j] = block.actionSwing[j].text for j in range(2) : if section.safeNoneSwing[j].isSelected() : section.safePoint[j] = None if section.brakeNoneSwing[j].isSelected() : section.brakeBlock[j] = None self.reDisplay() AutoDispatcher.setPreferencesDirty() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Blocks changes applied") # Locations Window ================= class ADlocationsFrame (AdScrollFrame) : def __init__(self) : # Create Locations window # Retrieve location names # super.init AdScrollFrame.__init__(self, "Locations", JLabel("Define the list" + " of sections corresponding to each Operations' location")) frameSize = self.getMinimumSize() if frameSize.width < 800 : frameSize.width = 800 self.setMinimumSize(frameSize) self.pack() def createHeader(self): # Fill contents of Header self.header.setLayout(GridLayout(1, 2)) self.header.add(AutoDispatcher.centerLabel("Location")) self.header.add(AutoDispatcher.centerLabel("Sections")) def createDetail(self): # Fill contents of scroll area # Compute total number of lines self.locationNames = ADlocation.getNames() self.locationNames.sort() self.detail.setLayout(GridLayout(len(self.locationNames), 2)) self.valuesSwing = [] for locationName in self.locationNames : location = ADlocation.getByName(locationName) if location.opLocation == None : locationName += " (Unknown)" self.detail.add(JLabel(locationName)) value = location.text value = JTextField(value, 20) self.detail.add(value) self.valuesSwing.append(value) def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Apply button self.applyButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.applyButton) # Buttons of Locations window ================= # define what Cancel button in Locations Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.locationsFrame = None # define what Apply button in Locations Window does when clicked def whenApplyClicked(self,event) : ind = 0 for locationName in self.locationNames : location = ADlocation.getByName(locationName) location.setSections(self.valuesSwing[ind].text) ind += 1 AutoDispatcher.setPreferencesDirty() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Locations changes applied") # Preferences Window ================= class ADpreferencesFrame (AdScrollFrame) : def __init__(self) : # Create Preferences window # super.init AdScrollFrame.__init__(self, "Preferences", None) def createHeader(self): self.header = None def createDetail(self): self.detail.setLayout(GridLayout(43, 2)) self.detail.add(JLabel("COMMON SETTINGS")) self.detail.add(JLabel("")) self.detail.add(JLabel("Verbose output:")) self.verboseSwing = JCheckBox("", ADsettings.verbose) self.detail.add(self.verboseSwing) self.detail.add(JLabel("Ring bell for main events:")) self.ringBellSwing = JCheckBox("", ADsettings.ringBell) self.detail.add(self.ringBellSwing) self.detail.add(JLabel("Pause mode:")) self.pauseSwing = JComboBox(["Disabled", "Stop trains", "Emergency stop trains", "Turn power off"]) self.pauseSwing.setSelectedIndex(ADsettings.pauseMode) self.detail.add(self.pauseSwing) self.detail.add(JLabel("Turnouts controlled by separate system: ")) self.separateTurnoutsSwing = JCheckBox("", ADsettings.separateTurnouts) self.detail.add(self.separateTurnoutsSwing) self.detail.add(JLabel("Signals controlled by separate system: ")) self.separateSignalsSwing = JCheckBox("", ADsettings.separateSignals) self.detail.add(self.separateSignalsSwing) self.detail.add(JLabel("Scale: ")) self.scaleSwing = JTextField(str(ADsettings.scale), 5) temppane = JPanel() temppane.setLayout(BoxLayout(temppane, BoxLayout.X_AXIS)) temppane.add(JLabel("1:")) temppane.add(self.scaleSwing) self.detail.add(temppane) self.detail.add(JLabel("Flashing cycle of signal icons (seconds):")) self.flashingSwing = JTextField(str(ADsettings.flashingCycle), 5) self.detail.add(self.flashingSwing) self.detail.add(JLabel("")) self.detail.add(JLabel("")) self.detail.add(JLabel("DISPATCHER SETTINGS")) self.detail.add(JLabel("")) self.detail.add(JLabel("Derailed trains detection:")) self.derailedSwing = JComboBox(["Disabled", "Enabled: only warning", "Enabled: pause script"]) self.derailedSwing.setSelectedIndex(ADsettings.derailDetection) self.detail.add(self.derailedSwing) self.detail.add(JLabel("Trains entering wrong route detection: ")) self.wrongRouteSwing = JComboBox(["Disabled", "Enabled: only warning", "Enabled: pause script"]) self.wrongRouteSwing.setSelectedIndex( ADsettings.wrongRouteDetection) self.detail.add(self.wrongRouteSwing) self.detail.add(JLabel("Stalled trains detection: ")) self.stalledSwing = JComboBox(["Disabled", "Enabled: only warning", "Enabled: pause script"]) self.stalledSwing.setSelectedIndex(ADsettings.stalledDetection) self.detail.add(self.stalledSwing) self.detail.add(JLabel( "Maximum time required to travel a block (seconds): ")) self.stalledTimeSwing = JTextField( str(float(ADsettings.stalledTime)/1000.), 5) self.detail.add(self.stalledTimeSwing) self.detail.add(JLabel("Lost cars detection:")) self.lostCarsSwing = JComboBox(["Disabled", "Enabled: only warning", "Enabled: pause script"]) self.lostCarsSwing.setSelectedIndex(ADsettings.lostCarsDetection) self.detail.add(self.lostCarsSwing) if ADsettings.units == 1.0 : self.detail.add(JLabel("Tollerance for lost cars detection (mm.):")) elif ADsettings.units == 10.0 : self.detail.add(JLabel("Tollerance for lost cars detection (cm.):")) else : self.detail.add(JLabel( "Tolerance for lost cars detection (inches):")) self.lostLengthSwing = JTextField( str(ADsettings.lostCarsTollerance / ADsettings.units), 4) self.detail.add(self.lostLengthSwing) self.detail.add(JLabel( "Maximum number of sections occupied by a train:")) self.lostMaxSwing = JTextField( str(ADsettings.lostCarsSections), 4) self.detail.add(self.lostMaxSwing) self.detail.add(JLabel( "(Most) cars are equipped with resistive wheel-sets:")) self.useResistiveSwing = JCheckBox("", ADsettings.resistiveDefault) self.detail.add(self.useResistiveSwing) self.detail.add(JLabel("Train length expressed in: ")) self.unitsSwing = JComboBox(["mm.", "cm.", "inches"]) if ADsettings.units == 1.0 : self.unitsSwing.setSelectedIndex(0) elif ADsettings.units == 10.0 : self.unitsSwing.setSelectedIndex(1) else : self.unitsSwing.setSelectedIndex(2) self.detail.add(self.unitsSwing) self.detail.add(JLabel("Release sections based on train lenght: ")) self.useLengthSwing = JCheckBox("", ADsettings.useLength) self.detail.add(self.useLengthSwing) if ADblock.blocksWithLength == 0 : self.useLengthSwing.setEnabled(False) self.detail.add(JLabel(" (Block lengths not defined!) ")) elif ADblock.blocksWithoutLength == 0 : self.detail.add(JLabel(" (Length defined for all blocks)")) else : self.detail.add(JLabel(" (Length not defined for " + str(ADblock.blocksWithoutLength) + " blocks!)")) self.detail.add(JLabel("")) self.detail.add(JLabel("Number of sections ahead to be allocated: ")) self.aheadSwing = JComboBox(["1", "2", "3", "4", "5"]) self.aheadSwing.setSelectedIndex(ADsettings.allocationAhead -1) self.detail.add(self.aheadSwing) self.detail.add(JLabel("Locomotives maintenance interval (hours): ")) self.maintenanceSwing = JTextField(str(ADsettings.maintenanceTime), 5) self.detail.add(self.maintenanceSwing) if ADsettings.units == 25.4 : self.detail.add(JLabel( "Mileage maintenance interval (scale miles): ")) multiplier = ADsettings.scale / 1609344. else : self.detail.add(JLabel( "Mileage maintenance interval (scale Km.): ")) multiplier = ADsettings.scale / 1000000. self.milesSwing = JTextField(str(round(ADsettings.maintenanceMiles * multiplier,1)), 5) self.detail.add(self.milesSwing) self.detail.add(JLabel("Override JMRI block tracking:")) self.blockTrackingSwing = JCheckBox("", ADsettings.blockTracking) self.detail.add(self.blockTrackingSwing) self.detail.add(JLabel("Update JMRI sections state:")) self.sectionTrackingSwing = JCheckBox("", ADsettings.sectionTracking) self.detail.add(self.sectionTrackingSwing) self.detail.add(JLabel("Trust turnouts KnownState:")) self.trustTurnoutsSwing = JCheckBox("", ADsettings.trustTurnouts) self.detail.add(self.trustTurnoutsSwing) self.detail.add(JLabel("Delay between turnout commands (seconds): ")) self.turnoutDelaySwing = JTextField( str(float(ADsettings.turnoutDelay)/1000.), 5) self.detail.add(self.turnoutDelaySwing) self.detail.add(JLabel("Delay before clearing signals (seconds): ")) self.clearDelaySwing = JTextField( str(float(ADsettings.clearDelay)/1000.), 5) self.detail.add(self.clearDelaySwing) self.detail.add(JLabel("Trust signals KnownState:")) self.trustSignalsSwing = JCheckBox("", ADsettings.trustSignals) self.detail.add(self.trustSignalsSwing) self.detail.add(JLabel("Delay between signal commands (seconds): ")) self.signalDelaySwing = JTextField( str(float(ADsettings.signalDelay)/1000.), 5) self.detail.add(self.signalDelaySwing) self.detail.add(JLabel("Automatically restart trains at script startup:")) self.autoRestartSwing = JCheckBox("", ADsettings.autoRestart) self.detail.add(self.autoRestartSwing) self.detail.add(JLabel("")) self.detail.add(JLabel("")) self.detail.add(JLabel("ENGINEER SETTINGS")) self.detail.add(JLabel("")) self.detail.add(JLabel("In front of red signals:")) self.stopModeSwing = JComboBox(["Progressively stop train", "Immediately stop train"]) self.stopModeSwing.setSelectedIndex(ADsettings.stopMode) self.detail.add(self.stopModeSwing) self.detail.add(JLabel( "Delay between clear signal and train departure (seconds): ")) self.detail1 = JPanel() self.startDelayMinSwing = JTextField( str(float(ADsettings.startDelayMin)/1000.), 5) self.startDelayMaxSwing = JTextField( str(float(ADsettings.startDelayMax)/1000.), 5) self.detail1.add(JLabel("Between ")) self.detail1.add(self.startDelayMinSwing) self.detail1.add(JLabel(" and ")) self.detail1.add(self.startDelayMaxSwing) self.detail.add(self.detail1) self.detail.add(JLabel("Default actions before train departure:")) self.defaultStartSwing = JTextField(ADsettings.defaultStartAction, 5) self.detail.add(self.defaultStartSwing) self.detail.add(JLabel("Switch headlights ON/OFF:")) self.lightsSwing = JComboBox(["Never", "When train starts/stops", "When schedule starts/ends"]) self.lightsSwing.setSelectedIndex(ADsettings.lightMode) self.detail.add(self.lightsSwing) self.detail.add(JLabel("Delay between throttle commands (seconds):")) self.dccDelaySwing = JTextField( str(float(ADsettings.dccDelay)/1000.), 5) self.detail.add(self.dccDelaySwing) self.detail.add(JLabel( "Maximum interval between throttle commands (seconds): ")) self.maxIdleSwing = JTextField( str(float(ADsettings.maxIdle)/1000.), 5) self.detail.add(self.maxIdleSwing) self.detail.add(JLabel("Acceleration/deceleration interval: ")) self.speedRampSwing = JComboBox(["1/10 sec.", "2/10 sec.", "3/10 sec.", "4/10 sec.", "5/10 sec."]) self.speedRampSwing.setSelectedIndex(ADsettings.speedRamp -1) self.detail.add(self.speedRampSwing) self.detail.add(JLabel("Enable self-adjustment of braking distance: ")) self.selfLearningSwing = JCheckBox("", ADsettings.selfLearning) if AutoDispatcher.simulation : self.selfLearningSwing.enabled = False self.detail.add(self.selfLearningSwing) def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Apply button (don't use the default one, since we wish # the button always on) self.setButton = JButton("Apply") self.setButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.setButton) # Buttons of Preferences window ================= # define what Cancel button in Preferences Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.preferencesFrame = None # define what Apply button in Preferences Window does when clicked def whenApplyClicked(self,event) : ADsettings.verbose = self.verboseSwing.isSelected() self.ringBellSwing.isSelected() ADsettings.ringBell = self.ringBellSwing.isSelected() ADsettings.pauseMode = self.pauseSwing.getSelectedIndex() ADsettings.separateTurnouts = ( self.separateTurnoutsSwing.isSelected()) ADsettings.separateSignals = ( self.separateSignalsSwing.isSelected()) try : ADsettings.scale = float(self.scaleSwing.text) except : ADsettings.scale = 1 self.scaleSwing.text = "1" try : ADsettings.flashingCycle = float(self.flashingSwing.text) except : ADsettings.flashingCycle = 1.0 self.flashingSwing.text = "1" ADsettings.derailDetection = self.derailedSwing.getSelectedIndex() ADsettings.stalledDetection = self.stalledSwing.getSelectedIndex() try : ADsettings.stalledTime = int( float(self.stalledTimeSwing.text)*1000.) except : ADsettings.stalledTime = 1000 self.stalledTimeSwing.text = "1" ADsettings.lostCarsDetection = ( self.lostCarsSwing.getSelectedIndex()) oldValue = ADsettings.lostCarsTollerance try : ADsettings.lostCarsTollerance = int( float(self.lostLengthSwing.text) * ADsettings.units) except : ADsettings.lostCarsTollerance = oldValue self.lostLengthSwing.text = str(oldValue / ADsettings.units) oldValue = ADsettings.lostCarsTollerance try : ADsettings.lostCarsSections = int( self.lostMaxSwing.text) except : ADsettings.lostCarsSections = oldValue self.lostMaxSwing.text = str(oldValue) ADsettings.wrongRouteDetection = ( self.wrongRouteSwing.getSelectedIndex()) i = self.unitsSwing.getSelectedIndex() if i == 0 : newUnits = 1.0 elif i == 1 : newUnits = 10.0 else : newUnits = 25.4 if newUnits != ADsettings.units : ADsettings.units = newUnits if AutoDispatcher.trainsFrame != None : AutoDispatcher.trainsFrame.reDisplay() AutoDispatcher.preferencesFrame.show() ADsettings.useLength = self.useLengthSwing.isSelected() ADsettings.resistiveDefault = self.useResistiveSwing.isSelected() ADsettings.allocationAhead = (self.aheadSwing.getSelectedIndex() + 1) ADsettings.blockTracking = self.blockTrackingSwing.isSelected() ADsettings.sectionTracking = ( self.sectionTrackingSwing.isSelected()) ADsettings.trustTurnouts = self.trustTurnoutsSwing.isSelected() try : ADsettings.turnoutDelay = int( float(self.turnoutDelaySwing.text)*1000.) except : ADsettings.turnoutDelay = 1000 self.turnoutDelaySwing.text = "1" try : ADsettings.clearDelay = int( float(self.clearDelaySwing.text)*1000.) except : ADsettings.clearDelay = 0 self.clearDelaySwing.text = "0" ADsettings.trustSignals = self.trustSignalsSwing.isSelected() try : ADsettings.signalDelay = int( float(self.signalDelaySwing.text)*1000.) except : ADsettings.signalDelay = 0 self.signalDelaySwing.text = "0" ADsettings.autoRestart = self.autoRestartSwing.isSelected() try : ADsettings.maintenanceTime = float(self.maintenanceSwing.text) except : ADsettings.maintenanceTime = 0 self.maintenanceSwing.text = "0" try : if ADsettings.units == 25.4 : multiplier = 1609344. / ADsettings.scale else : multiplier = 1000000. / ADsettings.scale ADsettings.maintenanceMiles = (float(self.milesSwing.text) * multiplier) except : ADsettings.maintenanceMiles = 0 self.milesSwing.text = "0" try : ADsettings.dccDelay = int( float(self.dccDelaySwing.text)*1000.) except : ADsettings.dccDelay = 10 self.dccDelaySwing.text = "0.01" ADsettings.stopMode = self.stopModeSwing.getSelectedIndex() try : ADsettings.startDelayMin = int( float(self.startDelayMinSwing.text)*1000.) except : ADsettings.startDelayMin = 0 self.startDelayMinSwing.text = "0" try : ADsettings.startDelayMax = int( float(self.startDelayMaxSwing.text)*1000.) except : ADsettings.startDelayMax = 0 self.startDelayMaxSwing.text = "0" ADsettings.defaultStartAction = self.defaultStartSwing.text ADsettings.lightMode = self.lightsSwing.getSelectedIndex() ADsettings.speedRamp = self.speedRampSwing.getSelectedIndex() + 1 try : ADsettings.maxIdle = int(float(self.maxIdleSwing.text)*1000.) except : ADsettings.maxIdle = 0 self.maxIdleSwing.text = "0" ADsettings.selfLearning = self.selfLearningSwing.isSelected() AutoDispatcher.setPreferencesDirty() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Preferences changes applied") # Sound List Window ================= class ADsoundListFrame (AdScrollFrame) : def __init__(self) : # Create Sound List window # super.init AdScrollFrame.__init__(self, "List of Sounds", None) def createHeader(self): header1 = JPanel() header1.setLayout(GridLayout(1, 4)) header1.add(AutoDispatcher.centerLabel("Name")) header1.add(JLabel("")) header1.add(JLabel("")) header1.add(JLabel("")) self.header.setLayout(GridLayout(1, 2)) self.header.add(header1) self.header.add(AutoDispatcher.centerLabel("Path")) def createDetail(self): self.detail.setLayout(GridLayout(len(ADsettings.soundList), 2)) self.namesSwing = [] ind = 0 for s in ADsettings.soundList : temppane = JPanel() temppane.setLayout(GridLayout(1, 4)) nameSwing = JTextField(s.name, 5) self.namesSwing.append(nameSwing) temppane.add(nameSwing) browseButton = JButton("Browse") browseButton.setActionCommand(str(ind)) browseButton.actionPerformed = self.whenBrowseClicked temppane.add(browseButton) if ind == 0 : temppane.add(JLabel("")) else : deleteButton = JButton("Delete") deleteButton.setActionCommand(str(ind)) deleteButton.actionPerformed = self.whenDeleteClicked temppane.add(deleteButton) playButton = JButton("Play") playButton.setActionCommand(str(ind)) playButton.actionPerformed = self.whenPlayClicked temppane.add(playButton) self.detail.add(temppane) self.detail.add(JLabel(s.path)) ind += 1 def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Add button self.addButton = JButton("Add") self.addButton.actionPerformed = self.whenAddClicked self.buttons.add(self.addButton) # Apply button self.setButton = JButton("Apply") self.setButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.setButton) # Buttons of Sound List window ================= # define what Cancel button in Sound List Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.soundListFrame = None # define what Add button in Sound List Window does when clicked def whenAddClicked(self,event) : ADsettings.soundList.append(ADsound("New sound")) self.soundListChanged() # define what Apply button in Sound List Window does when clicked def whenApplyClicked(self,event) : ind = 0 for s in ADsettings.soundList : s.name = self.namesSwing[ind].text ind += 1 AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Sound list updated") self.soundListChanged() # define what Browse button in Sound List Window does when clicked def whenBrowseClicked(self,event) : ind = int(event.getActionCommand()) fc = JFileChooser(ADsettings.soundRoot) fc.addChoosableFileFilter(ADsoundFilter()) retVal = fc.showOpenDialog(None) if retVal != JFileChooser.APPROVE_OPTION : return file = fc.getSelectedFile() if file == None : return ADsettings.soundRoot = file.getParent() ADsettings.soundList[ind].setPath(file.getPath()) if self.namesSwing[ind].text != "New sound" : ADsettings.soundList[ind].name = self.namesSwing[ind].text if ADsettings.soundList[ind].name == "New sound" : fileName = file.getName() upperName = fileName.upper() if upperName.endswith(".WAV") : fileName = fileName[0:len(fileName)-4] if upperName.endswith(".AU") : fileName = fileName[0:len(fileName)-4] ADsettings.soundList[ind].name = fileName self.soundListChanged() # define what Delete button in Sound List Window does when clicked def whenDeleteClicked(self,event) : ind = int(event.getActionCommand()) name = ADsettings.soundList[ind].name if (JOptionPane.showConfirmDialog(None, "Remove sound \"" + name + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) : return ADsettings.soundList.pop(ind) AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Sound \"" + name + "\" removed") self.soundListChanged() # define what Play button in Sound List Window does when clicked def whenPlayClicked(self,event) : ind = int(event.getActionCommand()) ADsettings.soundList[ind].play() def soundListChanged(self) : if AutoDispatcher.soundDefaultFrame != None : AutoDispatcher.soundDefaultFrame.reDisplay() ADsettings.newSoundDic() AutoDispatcher.setPreferencesDirty() self.reDisplay() class ADsoundFilter (FileFilter) : def __init__(self) : FileFilter.__init__(self) def accept(self, f) : if f.isDirectory() : return True name = str(f.getName()).upper() if (name.endswith(".WAV") or name.endswith(".AU")) : return True; return False; def getDescription(self) : return "Sound Clip (*.wav, *.au)" # Sound Default Window ================= class ADsoundDefaultFrame (AdScrollFrame) : def __init__(self) : # Create Sound Default window # super.init AdScrollFrame.__init__(self, "Default Sounds", None) def createHeader(self): self.header.setLayout(GridLayout(1, 3)) self.header.add(AutoDispatcher.centerLabel("Event")) self.header.add(AutoDispatcher.centerLabel("Sound")) self.header.add(JLabel("")) def createDetail(self): self.detail.setLayout(GridLayout(len(ADsettings.soundLabel), 3)) self.soundsSwing = [] sounds = ["None"] for s in ADsettings.soundList : sounds.append(s.name) ind = 0 for s in ADsettings.soundLabel : self.detail.add(JLabel(s)) soundSwing = JComboBox(sounds) soundSwing.setSelectedIndex(ADsettings.defaultSounds[ind]) self.soundsSwing.append(soundSwing) self.detail.add(soundSwing) playButton = JButton("Play") playButton.setActionCommand(str(ind)) playButton.actionPerformed = self.whenPlayClicked if ADsettings.defaultSounds[ind] < 1 : playButton.enabled = False self.detail.add(playButton) ind += 1 def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Apply button self.setButton = JButton("Apply") self.setButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.setButton) # Buttons of Sound Default window ================= # define what Cancel button in Sound List Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.soundDefaultFrame = None # define what Apply button in Sound Default Window does when clicked def whenApplyClicked(self,event) : ind = 0 for s in self.soundsSwing : ADsettings.defaultSounds[ind] = s.getSelectedIndex() ind += 1 AutoDispatcher.setPreferencesDirty() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Default sounds changed") self.reDisplay() # define what Play button in Sound List Window does when clicked def whenPlayClicked(self,event) : ind = int(event.getActionCommand()) ind = self.soundsSwing[ind].getSelectedIndex()-1 if ind < 0 : return ADsettings.soundList[ind].play() # Locomotives Window ================= class ADlocosFrame (AdScrollFrame) : def __init__(self) : # Create Locomotives window # super.init AdScrollFrame.__init__(self, "Locomotives", None) def createHeader(self): self.columns = len(ADsettings.speedsList) + 8 self.header.setLayout(GridLayout(1, self.columns)) self.header.add(AutoDispatcher.centerLabel("Loco")) self.header.add(AutoDispatcher.centerLabel("Addr.")) for s in ADsettings.speedsList : self.header.add(AutoDispatcher.centerLabel(s)) self.header.add(AutoDispatcher.centerLabel("Acc.")) self.header.add(AutoDispatcher.centerLabel("Dec.")) self.header.add(AutoDispatcher.centerLabel("Throttle")) runTime = JLabel("RunTime") runTime.setHorizontalAlignment(JLabel.RIGHT) self.header.add(runTime) if ADsettings.units == 25.4 : miles = JLabel("Miles") else : miles = JLabel("Km.") miles.setHorizontalAlignment(JLabel.RIGHT) self.header.add(miles) self.header.add(JLabel("")) def createDetail(self): # Fill contents of scroll area self.locos = ADlocomotive.getNames() self.locos.sort() self.detail.setLayout(GridLayout(len(self.locos), self.columns)) ind = 0 for ll in self.locos : l = ADlocomotive.getByName(ll) self.detail.add(AutoDispatcher.centerLabel(str(l.name))) self.detail.add(l.addressSwing) for s in l.speedSwing : self.detail.add(s) self.detail.add(l.accSwing) self.detail.add(l.decSwing) self.detail.add(l.currentSpeedSwing) l.outputMileage() self.detail.add(l.hoursSwing) self.detail.add(l.milesSwing) clearButton = JButton("Clear") clearButton.setActionCommand(str(ind)) clearButton.actionPerformed = self.whenClearClicked self.detail.add(clearButton) ind += 1 def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Apply button self.setButton = JButton("Apply") self.setButton.actionPerformed = self.whenApplyClicked self.buttons.add(self.setButton) # Remove button self.removeButton = JButton("Remove locos not in JMRI roster") self.removeButton.actionPerformed = self.whenRemoveClicked self.buttons.add(self.removeButton) # Buttons of Locomotives window ================= # define what Cancel button in Locomotives Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.locosFrame = None # define what Remove button in Locomotives Window does when clicked def whenRemoveClicked(self,event) : locos = ADlocomotive.getList() newDic = {} removed = 0 for l in locos : if l.inJmriRoster or l.usedBy != None : newDic[l.name] = l else : removed += 1 if removed > 0 : # Ask confirmation! if (JOptionPane.showConfirmDialog(None, "Remove " + str(removed) + " locomotives not included in JMRI roster?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) : removed = 0 if removed > 0 : ADlocomotive.locoIndex = newDic self.reDisplay() if AutoDispatcher.trainsFrame != None : AutoDispatcher.trainsFrame.reDisplay() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, str(removed) + " locomotives removed") AutoDispatcher.instance.saveLocomotives() else : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, " No locomotive removed") # define what Apply button in Locomotives Window does when clicked def whenApplyClicked(self,event) : for l in ADlocomotive.getList() : if not l.inJmriRoster : l.setAddress(int(l.addressSwing.text)) ss = [] for i in range(len(l.speedSwing)) : try : value = float(l.speedSwing[i].text) except : value = 0.5 l.speedSwing[i].text = "0.5" ss.append(value) l.setSpeedTable(ss) try : acc = int(l.accSwing.text) except : acc = 0 l.accSwing.text = "0" try : dec = int(l.decSwing.text) except : dec = 0 l.decSwing.text = "0" l.setMomentum(acc, dec) AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Locomotives changes applied") AutoDispatcher.instance.saveLocomotives() # define what Clear button in Locomotives Window does when clicked def whenClearClicked(self,event) : ind = int(event.getActionCommand()) locoName = self.locos[ind] loco = ADlocomotive.getByName(locoName) loco.runningTime = 0 loco.mileage = 0 AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Mileage and timer of locomotive \"" + locoName + "\" cleared") AutoDispatcher.instance.saveLocomotives() self.reDisplay() # Trains Window ================= class ADtrainsFrame (AdScrollFrame) : def __init__(self) : # Create Trains window temppane = JPanel() temppane1 = JPanel() temppane1.setLayout(BoxLayout(temppane1, BoxLayout.X_AXIS)) temppane1.add(JLabel( " Maximum number of trains running at once (0 = no limit) : ")) self.maxTrainsSwing = JTextField(str(ADsettings.max_trains), 4) temppane1.add(self.maxTrainsSwing) self.applyButton = JButton("Set") self.applyButton.actionPerformed = self.whenApplyClicked temppane1.add(self.applyButton) temppane.add(temppane1) # super.init AdScrollFrame.__init__(self, "Trains", temppane) def createHeader(self): header1 = JPanel() header1.setLayout(GridLayout(1, 2)) header1.add(AutoDispatcher.centerLabel("Train")) header1.add(AutoDispatcher.centerLabel("Direction")) header2 = JPanel() header2.setLayout(GridLayout(1, 2)) header2.add(AutoDispatcher.centerLabel("Section")) header2.add(AutoDispatcher.centerLabel("Locomotive")) header3 = JPanel() header3.setLayout(GridLayout(1, 2)) header3.add(AutoDispatcher.centerLabel("Reversed")) header3.add(AutoDispatcher.centerLabel("Dest./State")) header4 = JPanel() header4.setLayout(GridLayout(1, 2)) header4.add(AutoDispatcher.centerLabel("Speed")) header4.add(JLabel("")) header5 = JPanel() header5.setLayout(GridLayout(1, 2)) header5.add(JLabel("")) header5.add(JLabel("")) header6 = JPanel() header6.setLayout(GridLayout(1, 1)) header6.add(AutoDispatcher.centerLabel("Schedule")) self.header.setLayout(GridLayout(1, 6)) self.header.add(header1) self.header.add(header2) self.header.add(header3) self.header.add(header4) self.header.add(header5) self.header.add(header6) def createDetail(self): # Fill contents of scroll area locos = [] for l in ADlocomotive.getList() : if l.usedBy == None : locos.append(l.name) trains = ADtrain.getList() nTrains = len(trains) temppane1 = JPanel() temppane1.setLayout(GridLayout(nTrains, 2)) temppane2 = JPanel() temppane2.setLayout(GridLayout(nTrains, 2)) temppane3 = JPanel() temppane3.setLayout(GridLayout(nTrains, 2)) temppane4 = JPanel() temppane4.setLayout(GridLayout(nTrains, 2)) temppane5 = JPanel() temppane5.setLayout(GridLayout(nTrains, 2)) temppane6 = JPanel() temppane6.setLayout(GridLayout(nTrains, 1)) ind = 0 for t in trains : locosList = [t.locoName] locosList.extend(locos) locosList.sort() t.locoRoster = JComboBox(locosList) t.locoRoster.setSelectedItem(t.locoName) enableLoco = (t.engineerSwing.getSelectedItem() != "Manual" and not t.running ) t.locoRoster.setEnabled(enableLoco) t.reversedSwing.setEnabled(enableLoco) temppane1.add(t.nameSwing) temppane1.add(t.directionSwing) temppane2.add(t.sectionSwing) temppane2.add(t.locoRoster) temppane3.add(t.reversedSwing) temppane3.add(t.destinationSwing) temppane4.add(t.speedLevelSwing) t.detailButton.setActionCommand(str(ind)) t.detailButton.actionPerformed = self.whenDetailClicked temppane4.add(t.detailButton) temppane5.add(t.changeButton) temppane5.add(t.deleteButton) temppane6.add(t.scheduleSwing) ind += 1 self.detail.setLayout(GridLayout(1, 4)) self.detail.add(temppane1) self.detail.add(temppane2) self.detail.add(temppane3) self.detail.add(temppane4) self.detail.add(temppane5) self.detail.add(temppane6) def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Add button self.addButton = JButton("Add") self.addButton.actionPerformed = self.whenAddClicked self.buttons.add(self.addButton) # Import button self.importButton = JButton("Import") self.importButton.actionPerformed = self.whenImportClicked self.buttons.add(self.importButton) # Buttons of Trains window ================= # define what Cancel button in Locomotives Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.trainsFrame = None if AutoDispatcher.trainDetailFrame != None : AutoDispatcher.trainDetailFrame.dispose() AutoDispatcher.trainDetailFrame = None # define what Add button in Trains Window does when clicked def whenAddClicked(self,event) : ADtrain("New train") AutoDispatcher.setTrainsDirty() self.reDisplay() # define what Import button in Trains Window does when clicked def whenImportClicked(self,event) : if AutoDispatcher.importFrame == None : AutoDispatcher.importFrame = ADImportTrainFrame() else : AutoDispatcher.importFrame.reDisplay() # define what Detail button in Trains Window does when clicked def whenDetailClicked(self,event) : ind = int(event.getActionCommand()) if AutoDispatcher.trainDetailFrame != None : AutoDispatcher.trainDetailFrame.show() if ADtrain.trains[ind] == AutoDispatcher.trainDetailFrame.train : return if (JOptionPane.showConfirmDialog(None, "Save details of train \"" + AutoDispatcher.trainDetailFrame.train.getName() + "\" before editing details of train \"" + ADtrain.trains[ind].getName() + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) != 1) : AutoDispatcher.trainDetailFrame.train.whenSetClicked(None) AutoDispatcher.trainDetailFrame.dispose() AutoDispatcher.trainDetailFrame = ADtrainDetailFrame(ADtrain.trains[ind]) # define what Apply button in Trains Window does when clicked def whenApplyClicked(self,event) : try : ADsettings.max_trains = int(self.maxTrainsSwing.text) except : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Wrong maximum number of trains: " + self.maxTrainsSwing.text + " ignored") self.maxTrainsSwing.text = str(ADsettings.max_trains) return if ADsettings.max_trains < 0 : ADsettings.max_trains = 0 self.maxTrainsSwing.text = "0" AutoDispatcher.setPreferencesDirty() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Maximum number of trains set to " + str(ADsettings.max_trains)) def deleteTrain(self, train): # Routine to delete a train, called when the DEL button on train's # row is clicked # Release sections allocated to removed train train.releaseSections() # If the train has a locomotive, release it if train.locomotive != None : train.locomotive.usedBy = None # remove train from table ADtrain.remove(train) AutoDispatcher.setTrainsDirty() # force redisplay of Trains Window contents self.reDisplay() AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Train \"" + train.name + "\" removed") # Trains Window ================= class ADtrainDetailFrame (AdScrollFrame) : def __init__(self, train) : # Create Train Detail window self.train = train # super.init AdScrollFrame.__init__(self, "Train " + train.getName() + " detail", None) def createHeader(self): self.header = None def createDetail(self): # Fill contents of scroll area engineerList = AutoDispatcher.engineers.keys() engineerList.sort() engineerList.append("Manual") if self.train.locomotive == None : rows = 7 else : rows = 8 self.detail.setLayout(BoxLayout(self.detail, BoxLayout.Y_AXIS)) detail1 = JPanel() detail1.setLayout(GridLayout(rows, 2)) detail1.add(JLabel("Cars are equipped with resistive wheels: ")) detail1.add(self.train.resistiveSwing) detail1.add(JLabel("Actions before train departure: ")) self.train.startActionSwing.text = self.train.startAction detail1.add(self.train.startActionSwing) detail1.add(JLabel("Stop at beginning of sections that support this option: ")) detail1.add(self.train.canStopAtBeginningSwing) if ADsettings.units == 1.0 : detail1.add(JLabel("Train length (mm.), including" " locomotive: ")) elif ADsettings.units == 10.0 : detail1.add(JLabel("Train length (cm.), including" " locomotive: ")) else : detail1.add(JLabel("Train length (inches), including" " locomotive: ")) self.train.trainLengthSwing.text = str(round(self.train.trainLength / ADsettings.units,2)) detail1.add(self.train.trainLengthSwing) detail1.add(JLabel("Sections ahead to be allocated: ")) detail1.add(self.train.trainAllocationSwing) detail1.add(JLabel("Engineer: ")) self.train.engineerSwing.removeAllItems() for i in engineerList: self.train.engineerSwing.addItem(i) if self.train.engineerName in engineerList : self.train.engineerSwing.setSelectedItem(self.train.engineerName) else : self.train.engineerSwing.setSelectedItem("Auto") self.train.engineerAssigned = False detail1.add(self.train.engineerSwing) if self.train.locomotive != None : detail1.add(JLabel( "Clear braking history of this train for locomotive \"" + self.train.locoName + "\": ")) clearLoco = JButton("Clear history for current locomotive") clearLoco.actionPerformed = self.whenClearLocoClicked detail1.add(clearLoco) detail1.add(JLabel( "Clear braking history of this train for all locomotives: ")) clearAll = JButton("Clear history for all locomotives") clearAll.actionPerformed = self.whenClearAllClicked detail1.add(clearAll) self.detail.add(detail1) detail1 = JPanel() detail1.add(JLabel("Schedule")) self.detail.add(detail1) detail1 = JPanel() self.scheduleSwing = JTextArea(self.train.scheduleSwing.text, 4, 60) self.scheduleSwing.setLineWrap(True) self.scheduleSwing.setWrapStyleWord(True) detail1.add(JScrollPane(self.scheduleSwing)) self.detail.add(detail1) detail1 = JPanel() detail1.add(JLabel("Train speeds correspondence")) self.detail.add(detail1) detail1 = JPanel() detail1.setLayout(GridLayout(len(ADsettings.speedsList)+1, 2)) detail1.add(JLabel("Instead of")) detail1.add(JLabel("Use")) self.trainSpeedSwing = [] ind = 1 max = len(ADsettings.speedsList)-1 for s in ADsettings.speedsList : if ind > len(self.train.trainSpeed) : self.train.trainSpeed.append(ind) detail1.add(JLabel(s)) speedSwing = JComboBox(ADsettings.speedsList) i = self.train.trainSpeed[ind-1]-1 if i > max : i = max speedSwing.setSelectedIndex(i) self.trainSpeedSwing.append(speedSwing) detail1.add(speedSwing) ind += 1 self.detail.add(detail1) def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Apply button self.buttons.add(self.train.setButton) # Buttons of Train Detail window ================= # define what Cancel button in Train Detail Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.trainDetailFrame = None # define what Clear Loco button in Train Detail Window does when clicked def whenClearLocoClicked(self,event) : self.train.clearBrakingHistory(self.train.locomotive) AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Braking history for locomotive cleared") # define what Clear All button in Train Detail Window does when clicked def whenClearAllClicked(self,event) : self.train.clearBrakingHistory(None) AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Braking history for train cleared") # Import Trains Window ================= class ADImportTrainFrame (AdScrollFrame) : def __init__(self) : # Create Operations Interface window # Allows user to import an Operations train in AutoDispatcher # super.init AdScrollFrame.__init__(self, "Import train", JLabel("Choose train to be imported")) def createHeader(self): self.header.setLayout(GridLayout(1, 3)) self.header.add(AutoDispatcher.centerLabel("Train name")) self.header.add(AutoDispatcher.centerLabel("Status")) self.header.add(JLabel("")) def createDetail(self): # Fill contents of scroll area trainManager = TrainManager.instance() trainIds = trainManager.getTrainsByNameList() nTrains = len(trainIds) self.detail.setLayout(GridLayout(nTrains, 3)) self.opTrains = [] for i in range(nTrains) : opTrain = trainManager.getTrainById(trainIds[i]) self.opTrains.append(opTrain) self.detail.add(JLabel(opTrain.getName())) if opTrain.getBuilt() : self.detail.add(AutoDispatcher.centerLabel("Built")) importButton = JButton("Import") importButton.setActionCommand(str(i)) importButton.actionPerformed = self.whenImportClicked self.detail.add(importButton) else : self.detail.add(JLabel("")) self.detail.add(JLabel("")) def createButtons(self) : # Cancel button self.cancelButton.actionPerformed = self.whenCancelClicked self.buttons.add(self.cancelButton) # Buttons of Import Trains window ================= # define what Cancel button in Import Trains Window does when clicked def whenCancelClicked(self,event) : AdScrollFrame.dispose(self) AutoDispatcher.importFrame = None # define what Import button in Import Trains Window does when clicked def whenImportClicked(self,event) : ind = int(event.getActionCommand()) # Get Operations train opTrain = self.opTrains[ind] name = opTrain.getIconName() engine = opTrain.getLeadEngine() if engine == None : engine = "" else : engine = engine.getNumber() # Get the list of cars to be hauled by the train carManager = CarManager.instance() carIds = carManager.getCarsByTrainList(opTrain) cars = [] for carId in carIds : cars.append(carManager.getCarById(carId)) # Get train route route = opTrain.getRoute() routeIds = route.getLocationsBySequenceList() routeLocations = [] for id in routeIds : routeLocations.append(route.getLocationById(id)) # Now build our schedule departStation = True schedule = "" previousDirection = startDirection = "" startLocation = None hauled = [] while len(routeLocations) > 0 : routeLocation = routeLocations.pop(0) direction = routeLocation.getTrainDirectionString().upper() location = ADlocation.getByName(routeLocation.getName()) manualSwitching = "" # Check if any car must be picked up or dropped here for car in cars : source = car.getRouteLocation() destination = car.getRouteDestination() if source == destination : continue if source == routeLocation and not car in hauled : pickUp = True # does train pass twice in this location ? if routeLocation in routeLocations : # Yes. See if car must be picked up now or next time for nextLocation in routeLocations : if nextLocation == routeLocation : pickUp = False break if nextLocation == destination : break if pickUp : hauled.append(car) manualSwitching = " $M" elif destination == routeLocation and car in hauled : manualSwitching = " $M" hauled.remove(car) if departStation : startDirection = direction manualSwitching = "" startLocation = location routeStart = routeLocation departStation = False if location != None : if len(schedule) > 0 : schedule += " " if previousDirection != direction : schedule += "$" + direction + " " previousDirection = direction schedule += "[" + location.text + "]" + manualSwitching train = ADtrain(name) train.opTrain = opTrain if startDirection.strip() != "" : train.setDirection(startDirection) if engine != None and ADlocomotive.getByName(engine) != None : train.changeLocomotive(engine) else : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Locomotive \"" + engine + "\" not found. Manual running assumed!") train.setEngineer("Manual") # Find start section if startLocation != None : for section in startLocation.getSections() : section.setManual(True) if section.getAllocated() == None : train.setSection(section, True) if section.getAllocated() == train : break section.setManual(False) if section.getAllocated() != train : AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Canot place train \"" + name + "\" in location " + startLocation.name + " (all sections occupied)") train.trainLength = round(float(routeStart.getTrainLength()) * 304.8 / ADsettings.scale) if schedule.strip() != "" : train.setSchedule(schedule) train.updateSwing() AutoDispatcher.setTrainsDirty() if AutoDispatcher.trainsFrame != None : AutoDispatcher.trainsFrame.reDisplay() self.whenCancelClicked(None) class ADexamples1 : # DEFAULT DATA FOR EXAMPLE PANELS ========================== # Used if no setting file is found # Settings are chosen on the basis of layout Title examples = {} exampleTrains = {} examples["JavaOne remake"] = ( '2.0 Beta', ('CCW', 'CW'), 'N1', 'W', 1000, 0, 1, 0, 1, 2, 2, 30000 , 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA', 'CYAN'] , 1, [['E', '', 'CCW-CW', 'HEccw', 'HEcw', '', ''], ['N1', '', '' , 'HN1ccw', 'HN1cw', '', ''], ['N2', '', '', 'HN2ccw', 'HN2cw', '' , ''], ['S1', '', '', 'HS1ccw', 'HS1cw', '', ''], ['S2', '', '' , 'HS2ccw', 'HS2cw', '', ''], ['W', '', 'CCW-CW', 'HWccw', 'HWcw', '' , '']], [['Ea', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '' , '', '', ''], ['Eb', '', '', '', '', 'BRAKE', '', '', '', '', '', '' , ''], ['Ec', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'Ed', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''] , ['N1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['N1b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['N1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'N1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', '' ], ['N2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['N2b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['N2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'N2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', '' ], ['S1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['S1b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['S1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'S1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', '' ], ['S2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['S2b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['S2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'S2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', '' ], ['Wa', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['Wb', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], [ 'Wc', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Wd', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', '']], 1, 1 , 0, 25.4, 0, 1, 0, ['Min.', 'Low', 'Med.', 'High', 'Max.'], 2, 10, 0 , 2, 1, [['Stop', -1, -1, 0], ['Clear', -1, -1, 3]], 1, [['Single ' 'Head', [['Red'], ['Green']], [-1, -1]]], [['HXbccw', 'Single Head' , ['HXbccw']], ['HWcw', 'Single Head', ['HWcw']], ['HEcw', 'Single ' 'Head', ['HEcw']], ['HS1cw', 'Single Head', ['HS1cw']], ['HSWcw' , 'Single Head', ['HSWcw']], ['HSEcw', 'Single Head', ['HSEcw']], [ 'HNWccw', 'Single Head', ['HNWccw']], ['HSWccw', 'Single Head', [ 'HSWccw']], ['HWccw', 'Single Head', ['HWccw']], ['HN1cw', 'Single ' 'Head', ['HN1cw']], ['HNWcw', 'Single Head', ['HNWcw']], ['HXacw' , 'Single Head', ['HXacw']], ['HNEcw', 'Single Head', ['HNEcw']], [ 'HNEccw', 'Single Head', ['HNEccw']], ['HN1ccw', 'Single Head', [ 'HN1ccw']], ['HSEccw', 'Single Head', ['HSEccw']], ['HS2cw', 'Single ' 'Head', ['HS2cw']], ['HS1ccw', 'Single Head', ['HS1ccw']], ['HEccw' , 'Single Head', ['HEccw']], ['HN2ccw', 'Single Head', ['HN2ccw']], [ 'HS2ccw', 'Single Head', ['HS2ccw']], ['HXaccw', 'Single Head', [ 'HXaccw']], ['HN2cw', 'Single Head', ['HN2cw']], ['HXbcw', 'Single ' 'Head', ['HXbcw']]], 0, 0, 0, 1, 20000, 0, 1, 1, 1270, 3, 0, [['Bell' , 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1] , '', 12.0, 0.0, 87.0, [], 1.0) exampleTrains["JavaOne remake"] = [ ['T1017', 'N1', 'CCW', 0, '(3([N1 N2] [S1 S2]) $P25)', 0, 889.0, [0, 1 , 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1, 3 , 1, 7, 0, 1, 0, 10, 1, 0, 3, 1, 9], '1017', 0, [], 'Auto', 'N1', [1 , 2, 3, 4, 5], {}], ['T1019', 'S1', 'CW', 0, '(2([S1 S2] [N1 N2]) ' '$P15)', 0, 508.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 , 2, 0, 1, 0, 0, 0, 1, 2, 1, 7, 0, 1, 0, 10, 1, 0, 2, 1, 9], '1019' , 0, [], 'Auto', 'S1', [1, 2, 3, 4, 5], {}]] examples["Operations Example"] = ( '2.0 Beta', ['EAST', 'WEST'], 'Sv1', 'SvFr', 1000, 0, 2, 0, 1, 2, 2 , 0, 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA' , 'CYAN'], 1, [['Bf1', '', '', 'HBf1west', 'HBf1east', '', 'BFman'] , ['Bf2', '', '', 'HBf2west', 'HBf2east', '', 'BFman'], ['BfPa', '' , 'EAST-WEST', 'HBfPawest', 'HBfPaeast', '', ''], ['Dv1', '', '' , 'HDv1west', 'HDv1east', '', 'DVman'], ['Dv2', '', '', 'HDv2west' , 'HDv2east', '', 'DVman'], ['DvHb', '', 'EAST-WEST', 'HDvHbwest' , 'HDvHbeast', '', ''], ['Fa1', '', '', 'HFa1west', 'HFa1east', '' , 'FAman'], ['Fa2', '', '', 'HFa2west', 'HFa2east', '', 'FAman'], [ 'FaLv1', '', 'EAST-WEST+', 'HFaLv1west', 'HFaLv1east', '', ''], [ 'FaLv2', '', 'EAST-WEST+', 'HFaLv2west', 'HFaLv2east', '', ''], [ 'FaLv3', '', 'EAST-WEST+', 'HFaLv3west', 'HFaLv3east', '', ''], [ 'Fr1', '', '', 'HFr1west', 'HFr1east', '', 'FRman'], ['Fr2', '', '' , 'HFr2west', 'HFr2east', '', 'FRman'], ['FrBf', '', 'EAST-WEST' , 'HFrBfwest', 'HFrBfeast', '', ''], ['Hb1', '', '', 'HHb1west' , 'HHb1east', '', 'HBman'], ['Hb2', '', '', 'HHb2west', 'HHb2east' , '', 'HBman'], ['HbFa', '', 'EAST-WEST', 'HHbFawest', 'HHbFaeast' , '', ''], ['Lv1', '', '', 'HLv1west', 'HLv1cw', '', 'LVman'], ['Lv2' , '', '', 'HLv2west', 'HLv2cw', '', 'LVman'], ['Pa1', '', '' , 'HPa1west', 'HPa1east', '', 'PAman'], ['Pa2', '', '', 'HPa2west' , 'HPa2east', '', 'PAman'], ['PaDv', '', 'EAST-WEST', 'HPaDvwest' , 'HPaDveast', '', ''], ['Sv1', '', '', 'HSv1ccw', 'HSv1east', '' , 'SVman'], ['Sv2', '', '', 'HSv2ccw', 'HSv2east', '', 'SVman'], [ 'SvFr', '', 'EAST-WEST', 'HSvFrwest', 'HSvFreast', '', '']], [['Bf1a' , 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], [ 'Bf1b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['Bf1c' , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Bf1d', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [ 'Bf2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['Bf2b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['Bf2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'Bf2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '' , ''], ['BfPa1', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '' , '', '', ''], ['BfPa2', '', '', '', '', 'BRAKE', '', '', '', '', '' , '', ''], ['BfPa3', '', '', '', '', '', '', '', '', '', '', '', ''] , ['BfPa4', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'BfPa5', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '' , ''], ['Dv1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '' , '', '', ''], ['Dv1b', '', '', '', '', 'BRAKE', '', '', '', '', '' , '', ''], ['Dv1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE' , ''], ['Dv1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE' , '', '', ''], ['Dv2a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['Dv2b', '', '', '', '', 'BRAKE', '' , '', '', '', '', '', ''], ['Dv2c', '', '', '', '', '', '', '', '' , '', '', 'BRAKE', ''], ['Dv2d', '', 'ALLOCATE', '', '', '', '' , 'STOP', '', 'SAFE', '', '', ''], ['DvHb1', 'STOP', '', 'SAFE', '' , '', '', '', 'ALLOCATE', '', '', '', ''], ['DvHb2', '', '', '', '' , 'BRAKE', '', '', '', '', '', '', ''], ['DvHb3', '', '', '', '', '' , '', '', '', '', '', '', ''], ['DvHb4', '', '', '', '', '', '', '' , '', '', '', 'BRAKE', ''], ['DvHb5', '', 'ALLOCATE', '', '', '', '' , 'STOP', '', 'SAFE', '', '', ''], ['Fa1a', 'STOP', '', 'SAFE', '' , '', '', '', 'ALLOCATE', '', '', '', ''], ['Fa1b', '', '', '', '' , 'BRAKE', '', '', '', '', '', '', ''], ['Fa1c', '', '', '', '', '' , '', '', '', '', '', 'BRAKE', ''], ['Fa1d', '', 'ALLOCATE', '', '' , '', '', 'STOP', '', 'SAFE', '', '', ''], ['Fa2a', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['Fa2b', '' , '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['Fa2c', '', '' , '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Fa2d', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [ 'FaLv1', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['FaLv2', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['FaLv3', '', '', '', '', '', '', '', '', '', '', '', ''], ['FaLv4' , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['FaLv5', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [ 'LaFv6', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['FaLv7', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['FaLv8', '', '', '', '', '', '', '', '', '', '', '', ''], ['FaLv9' , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['FaLv10', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [ 'FaLv11', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['FaLv12', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '' ], ['FaLv13', '', '', '', '', '', '', '', '', '', '', '', ''], [ 'FaLv14', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'FaLv15', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '' , ''], ['Fr1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '' , '', '', ''], ['Fr1b', '', '', '', '', 'BRAKE', '', '', '', '', '' , '', ''], ['Fr1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE' , ''], ['Fr1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE' , '', '', ''], ['Fr2a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['Fr2b', '', '', '', '', 'BRAKE', '' , '', '', '', '', '', ''], ['Fr2c', '', '', '', '', '', '', '', '' , '', '', 'BRAKE', ''], ['Fr2d', '', 'ALLOCATE', '', '', '', '' , 'STOP', '', 'SAFE', '', '', ''], ['FrBf1', 'STOP', '', 'SAFE', '' , '', '', '', 'ALLOCATE', '', '', '', ''], ['FrBf2', '', '', '', '' , 'BRAKE', '', '', '', '', '', '', ''], ['FrBf3', '', '', '', '', '' , '', '', '', '', '', '', ''], ['FrBf4', '', '', '', '', '', '', '' , '', '', '', 'BRAKE', ''], ['FrBf5', '', 'ALLOCATE', '', '', '', '' , 'STOP', '', 'SAFE', '', '', ''], ['Hb1a', 'STOP', '', 'SAFE', '' , '', '', '', 'ALLOCATE', '', '', '', ''], ['Hb1b', '', '', '', '' , 'BRAKE', '', '', '', '', '', '', ''], ['Hb1c', '', '', '', '', '' , '', '', '', '', '', 'BRAKE', ''], ['Hb1d', '', 'ALLOCATE', '', '' , '', '', 'STOP', '', 'SAFE', '', '', ''], ['Hb2a', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['Hb2b', '' , '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['Hb2c', '', '' , '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Hb2d', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [ 'HbFa1', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['HbFa2', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['HbFa3', '', '', '', '', '', '', '', '', '', '', '', ''], ['HbFa4' , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['HbFa5', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [ 'Lv1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['Lv1b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['Lv1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'Lv1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '' , ''], ['Lv2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '' , '', '', ''], ['Lv2b', '', '', '', '', 'BRAKE', '', '', '', '', '' , '', ''], ['Lv2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE' , ''], ['Lv2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE' , '', '', ''], ['Pa1a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['Pa1b', '', '', '', '', 'BRAKE', '' , '', '', '', '', '', ''], ['Pa1c', '', '', '', '', '', '', '', '' , '', '', 'BRAKE', ''], ['Pa1d', '', 'ALLOCATE', '', '', '', '' , 'STOP', '', 'SAFE', '', '', ''], ['Pa2a', 'STOP', '', 'SAFE', '' , '', '', '', 'ALLOCATE', '', '', '', ''], ['Pa2b', '', '', '', '' , 'BRAKE', '', '', '', '', '', '', ''], ['Pa2c', '', '', '', '', '' , '', '', '', '', '', 'BRAKE', ''], ['Pa2d', '', 'ALLOCATE', '', '' , '', '', 'STOP', '', 'SAFE', '', '', ''], ['PaDv1', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['PaDv2', '' , '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['PaDv3', '', '' , '', '', '', '', '', '', '', '', '', ''], ['PaDv4', '', '', '', '' , '', '', '', '', '', '', 'BRAKE', ''], ['PaDv5', '', 'ALLOCATE', '' , '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['Sv1a', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['Sv1b', '' , '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['Sv1c', '', '' , '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Sv1d', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [ 'Sv2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['Sv2b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['Sv2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'Sv2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '' , ''], ['SvFr1', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '' , '', '', ''], ['SvFr2', '', '', '', '', 'BRAKE', '', '', '', '', '' , '', ''], ['SvFr3', '', '', '', '', '', '', '', '', '', '', '', ''] , ['SvFr4', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'SvFr5', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '' , '']], 1, 1, 0, 25.4, 0, 0, 0, ['Min.', 'Low', 'Med.', 'High' , 'Max.'], 3, 10, 0, 2, 1, [['Stop', -1, -1, 0], ['Clear', -1, -1, 5] , ['Approach', 0, -1, 3]], 1, [['Single Head', [['Red'], ['Green'], [ 'Yellow']], [-1, -1, -1]]], [['HFaLv3west', 'Single Head', [ 'HFaLv3west']], ['HDvHbccw', 'Single Head', ['HDvHbccw']], [ 'HFr1east', 'Single Head', ['HFr1east']], ['HFaLveast', 'Single Head' , ['HFaLveast']], ['HHbFacw', 'Single Head', ['HHbFacw']], [ 'HFaLv1east', 'Single Head', ['HFaLv1east']], ['HSv2east', 'Single ' 'Head', ['HSv2east']], ['HDv2west', 'Single Head', ['HDv2west']], [ 'HBf1west', 'Single Head', ['HBf1west']], ['HHb1east', 'Single Head' , ['HHb1east']], ['HHbFawest', 'Single Head', ['HHbFawest']], [ 'HSvFrccw', 'Single Head', ['HSvFrccw']], ['HFa2east', 'Single Head' , ['HFa2east']], ['HFr2west', 'Single Head', ['HFr2west']], [ 'HBfPawest', 'Single Head', ['HBfPawest']], ['HSvFreast', 'Single ' 'Head', ['HSvFreast']], ['HFaLv2west', 'Single Head', ['HFaLv2west']] , ['HFaLvccw', 'Single Head', ['HFaLvccw']], ['HSv1east', 'Single ' 'Head', ['HSv1east']], ['HDv1west', 'Single Head', ['HDv1west']], [ 'HHb2west', 'Single Head', ['HHb2west']], ['HLv2east', 'Single Head' , ['HLv2east']], ['HBfPaccw', 'Single Head', ['HBfPaccw']], [ 'HFa1east', 'Single Head', ['HFa1east']], ['HFrBfccw', 'Single Head' , ['HFrBfccw']], ['HFr1west', 'Single Head', ['HFr1west']], [ 'HFaLvwest', 'Single Head', ['HFaLvwest']], ['HSv1ccw', 'Single Head' , ['HSv1ccw']], ['HPa2east', 'Single Head', ['HPa2east']], [ 'HFaLv1west', 'Single Head', ['HFaLv1west']], ['HFaLvcw', 'Single ' 'Head', ['HFaLvcw']], ['HSv2west', 'Single Head', ['HSv2west']], [ 'HSv2ccw', 'Single Head', ['HSv2ccw']], ['HFrBfeast', 'Single Head' , ['HFrBfeast']], ['HFrBfcw', 'Single Head', ['HFrBfcw']], [ 'HPaDveast', 'Single Head', ['HPaDveast']], ['HSvFrcw', 'Single Head' , ['HSvFrcw']], ['HHb1west', 'Single Head', ['HHb1west']], ['HSv2cw' , 'Single Head', ['HSv2cw']], ['HDvHbeast', 'Single Head', [ 'HDvHbeast']], ['HFa2west', 'Single Head', ['HFa2west']], ['HSv1cw' , 'Single Head', ['HSv1cw']], ['HLv1east', 'Single Head', ['HLv1east' ]], ['HSvFrwest', 'Single Head', ['HSvFrwest']], ['HPa1east', 'Single' ' Head', ['HPa1east']], ['HBf2east', 'Single Head', ['HBf2east']], [ 'HHbFaccw', 'Single Head', ['HHbFaccw']], ['HSv1west', 'Single Head' , ['HSv1west']], ['HBfPacw', 'Single Head', ['HBfPacw']], ['HDvHbcw' , 'Single Head', ['HDvHbcw']], ['HFaLv3east', 'Single Head', [ 'HFaLv3east']], ['HLv2west', 'Single Head', ['HLv2west']], [ 'HFa1west', 'Single Head', ['HFa1west']], ['HDv2east', 'Single Head' , ['HDv2east']], ['HPa2west', 'Single Head', ['HPa2west']], [ 'HPaDvcw', 'Single Head', ['HPaDvcw']], ['HBf1east', 'Single Head', [ 'HBf1east']], ['HFrBfwest', 'Single Head', ['HFrBfwest']], ['HLv2cw' , 'Single Head', ['HLv2cw']], ['HPaDvwest', 'Single Head', [ 'HPaDvwest']], ['HHbFaeast', 'Single Head', ['HHbFaeast']], ['HLv1cw' , 'Single Head', ['HLv1cw']], ['HFr2east', 'Single Head', ['HFr2east' ]], ['HBfPaeast', 'Single Head', ['HBfPaeast']], ['HDvHbwest' , 'Single Head', ['HDvHbwest']], ['HFaLv2east', 'Single Head', [ 'HFaLv2east']], ['HLv1west', 'Single Head', ['HLv1west']], [ 'HDv1east', 'Single Head', ['HDv1east']], ['HPa1west', 'Single Head' , ['HPa1west']], ['HBf2west', 'Single Head', ['HBf2west']], [ 'HHb2east', 'Single Head', ['HHb2east']], ['HPaDvccw', 'Single Head' , ['HPaDvccw']]], 0, 0, 0, 1, 60000, 0, 1, 1, 2032, 3, 0, [['Bell' , 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1] , '', 0.0, 0.0, 87.0, [['Lakeview', 'Lv1 Lv2'], ['Hillsboro', 'Hb1 Hb2'], ['Fremont', 'Fr1 Fr2'], ['Port Arthur', 'Pa1 Pa2'], ['Danville', 'Dv1 Dv2'], ['Farmington', 'Fa1 Fa2'], ['Bakersfield', 'Bf1 Bf2'], ['Susanville', 'Sv1 Sv2']], 1.0) exampleTrains["Operations Example"] = [] examples["Reversing Track Example"] = ( '2.0 Beta', ('CCW', 'CW'), 'B1', 'B4', 1000, 0, 1, 0, 1, 2, 2, 0, 1 , ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA', 'CYAN'], 1 , [['B1', '', '', 'HB1cw', 'HB1ccw', '', 'B1man'], ['B2', '', '' , 'HB2cw', 'HB2ccw', '', 'B2man'], ['B3', '', 'CCW-CW', 'HB3cw' , 'HB3ccw', '', ''], ['B4', '', 'CCW-CW', 'HB4cw', 'HB4ccw', '', ''] , ['B5', '', '', 'HB5cw', 'HB5ccw', 'INVERTED', 'B5man'], ['B6', '' , '', 'HB6cw', 'HB6ccw', '', ''], ['B7', '', '', 'HB7cw', 'HB7ccw' , '', 'B7man']], [['B1a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['B1b', '', '', '', '', 'BRAKE', '' , '', '', '', '', '', ''], ['B1c', '', '', '', '', '', '', '', '', '' , '', 'BRAKE', ''], ['B1d', '', 'ALLOCATE', '', '', '', '', 'STOP' , '', 'SAFE', '', '', ''], ['B2a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['B2b', '', '', '', '', 'BRAKE', '' , '', '', '', '', '', ''], ['B2c', '', '', '', '', '', '', '', '', '' , '', 'BRAKE', ''], ['B2d', '', 'ALLOCATE', '', '', '', '', 'STOP' , '', 'SAFE', '', '', ''], ['B3a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['B3b', '', '', '', '', 'BRAKE', '' , '', '', '', '', '', ''], ['B3c', '', '', '', '', '', '', '', '', '' , '', '', ''], ['B3d', '', '', '', '', '', '', '', '', '', '' , 'BRAKE', ''], ['B3e', '', 'ALLOCATE', '', '', '', '', 'STOP', '' , 'SAFE', '', '', ''], ['B4a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['B4b', '', '', '', '', 'BRAKE', '' , '', '', '', '', 'BRAKE', ''], ['B4c', '', 'ALLOCATE', '', '', '' , '', 'STOP', '', 'SAFE', '', '', ''], ['B5a', 'STOP', '', 'SAFE', '' , '', '', '', 'ALLOCATE', '', '', '', ''], ['B5b', '', '', '', '' , 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B5c', '', 'ALLOCATE' , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B6a', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['B6b', '', '' , '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B6c', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B7e' , 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], [ 'B7d', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B7c' , '', '', '', '', '', '', '', '', '', '', '', ''], ['B7b', '', '', '' , '', '', '', '', '', '', '', 'BRAKE', ''], ['B7a', '', 'ALLOCATE' , '', '', '', '', 'STOP', '', 'SAFE', '', '', '']], 1, 1, 0, 25.4, 0 , 1, 0, ['Min.', 'Low', 'Med.', 'High', 'Max.'], 3, 10, 0, 2, 1, [[ 'Stop', -1, -1, 0], ['Clear', -1, -1, 5]], 1, [['Single Head', [[ 'Red'], ['Green']], [-1, -1]]], [['HB2ccw', 'Single Head', ['HB2ccw'] ], ['HB4cw', 'Single Head', ['']], ['HB5ccw', 'Single Head', [ 'HB5ccw']], ['HB1ccw', 'Single Head', ['HB1ccw']], ['HB5cw', 'Single ' 'Head', ['HB5cw']], ['HB4ccw', 'Single Head', ['']], ['HB1cw' , 'Single Head', ['HB1cw']], ['HB6cw', 'Single Head', ['HB6cw']], [ 'HB7ccw', 'Single Head', ['HB7ccw']], ['HB2cw', 'Single Head', [ 'HB2cw']], ['HB3ccw', 'Single Head', ['']], ['HB7cw', 'Single Head' , ['HB7cw']], ['HB3cw', 'Single Head', ['']], ['HB6ccw', 'Single ' 'Head', ['HB6ccw']]], 0, 0, 0, 1, 60000, 0, 1, 1, 2032, 3, 0, [[ 'Bell', 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1] , '', 0.0, 0.0, 87.0, [], 1.0) exampleTrains["Reversing Track Example"] = [ ['T1017', 'B1', 'CW', 0, '(2(B6 [B1 B2]) $P10)', 0, 508.0, [0, 1, 0 , 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1, 2, 1 , 3], '1017', 0, [], 'Auto', 'B1', [1, 2, 3, 4, 5], {}], ['T1633' , 'B2', 'CCW', 0, '(2(B6 [B1 B2]) $P15)', 0, 304.79999999999995, [0 , 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1 , 2, 0, 3], '1633', 0, [], 'Auto', 'B2', [1, 2, 3, 4, 5], {}], [ 'T3023', 'B5', 'CW', 0, '(2( B7 $P10 [B1 B2] B5) $P20)', 0, 762.0, [0 , 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1 , 2, 0, 3], '3023', 0, [], 'Auto', 'B5', [1, 2, 3, 4, 5], {}]] class ADexamples2 : # DEFAULT DATA FOR EXAMPLE PANELS ========================== # Examples are divided into two different classes to avoid exceeding Jython 64K limit examples = {} exampleTrains = {} examples["SignalMasts Example"] = ( '2.0 Beta', ('CCW', 'CW'), 'B30', 'B29', 1500, 0, 2, 0, 2, 2, 2 , 30000, 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA' , 'CYAN'], 1, [['B01', '', '', 'HB01cw', 'HB01ccw', '', 'B01man'], [ 'B02', '', '', 'HB02cw', 'HB02ccw', '', 'B02man'], ['B03', '', '' , 'HB03cw', 'HB03ccw', '', 'B03man'], ['B04', '', '', 'HB04cw' , 'HB04ccw', '', 'B04man'], ['B05', '', '', 'HB05cw', 'HB05ccw', '' , 'B05man'], ['B06', '', '', 'HB06cw', 'HB06ccw', '', 'B11man'], [ 'B07', '', '', 'HB07cw', 'HB07ccw', '', 'B11man'], ['B08', '', '' , 'HB08cw', 'HB08ccw', '', 'B11man'], ['B09', '', '', 'HB09cw' , 'HB09ccw', '', 'B09man'], ['B10', '', '', 'HB10cw', 'HB10ccw', '' , 'B10man'], ['B11', '', 'CCW-CW', 'HB11cw', 'HB11ccw', '', 'B11man'] , ['B12', '', 'CCW-CW', 'HB12cw', 'HB12ccw', '', 'B12man'], ['B13' , '', '', 'HB13cw', 'HB13ccw', '', 'B13man'], ['B14', '', '' , 'HB14cw', 'HB14ccw', '', 'B13man'], ['B15', '', '', 'HB15cw' , 'HB15ccw', '', 'B13man'], ['B16', '', '', 'HB16cw', 'HB16ccw', '' , 'B13man'], ['B17', '', 'CCW-CW', 'HB17cw', 'HB17ccw', '', 'B26man'] , ['B18', '', 'CCW-CW', 'HB18cw', 'HB18ccw', '', 'B28man'], ['B19' , 'CCW', '', 'HB19cw', 'HB19ccw', '', 'B19man'], ['B20', 'CW', '' , 'HB20cw', 'HB20ccw', '', 'B20man'], ['B21', 'CW', '', 'HB21cw' , 'HB21ccw', '', ''], ['B22', 'CCW', '', 'HB22cw', 'HB22ccw', '', ''] , ['B23', 'CW', '', 'HB23cw', 'HB23ccw', '', ''], ['B24', '', '' , 'HB24cw', 'HB24ccw', '', ''], ['B25', '', 'CW+', 'HB25cw' , 'HB25ccw', '', ''], ['B26', 'CCW', '', 'HB26cw', 'HB26ccw', '' , 'B26man'], ['B27', '', 'CCW', 'HB27cw', 'HB27ccw', '', 'B27man'], [ 'B28', '', 'CCW+', 'HB28cw', 'HB28ccw', '', 'B28man'], ['B29', '' , 'CCW-CW+', 'HB29cw', 'HB29ccw', '', 'B29man'], ['B30', '', '' , 'HB30cw', 'HB30ccw', '', 'B30man'], ['B31', '', '', 'HB31cw' , 'HB31ccw', '', 'B31man'], ['B32', '', '', 'HB32cw', 'HB32ccw', '' , 'B32man'], ['B33', 'CCW', 'CCW', 'HB33cw', 'HB33ccw', '', ''], [ 'B34', 'CW', 'CW', 'HB34cw', 'HB34ccw', '', '']], [['B1a', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B1b' , '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B1c', '' , '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B1d', '' , 'ALLOCATE', '', '30 MPH', '', '', 'STOP', '', 'SAFE', '', '', ''] , ['B2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '30 ' 'MPH', '', ''], ['B2b', '', '', '', '', 'BRAKE', '', '', '', '', '' , '', ''], ['B2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE' , ''], ['B2d', '', 'ALLOCATE', '', '30 MPH', '', '', 'STOP', '' , 'SAFE', '', '', ''], ['B3a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '30 MPH', '', ''], ['B3b', '', '', '', '', 'BRAKE' , '', '', '', '', '', '', ''], ['B3c', '', '', '', '', '', '', '', '' , '', '', 'BRAKE', ''], ['B3d', '', 'ALLOCATE', '', '30 MPH', '', '' , 'STOP', '', 'SAFE', '', '', ''], ['B4a', 'STOP', '', 'SAFE', '', '' , '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B4b', '', '', '', '' , 'BRAKE', '', '', '', '', '', '', ''], ['B4c', '', '', '', '', '' , '', '', '', '', '', 'BRAKE', ''], ['B4d', '', 'ALLOCATE', '', '30 ' 'MPH', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B5a', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B5b' , '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B5c', '' , '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B5d', '' , 'ALLOCATE', '', '30 MPH', '', '', 'STOP', '', 'SAFE', '', '', ''] , ['B6a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '' , 'BRAKE', ''], ['B6b', '', '', '', '', 'NO', '', '', '', '', '' , 'NO', ''], ['B6c', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP', '' , 'SAFE', '', '', ''], ['B7a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', 'BRAKE', ''], ['B7b', '', '', '', '', 'NO', '' , '', '', '', '', 'NO', ''], ['B7c', '', 'ALLOCATE', '', '', 'BRAKE' , '', 'STOP', '', 'SAFE', '', '', ''], ['B8a', 'STOP', '', 'SAFE', '' , '', '', '', 'ALLOCATE', '', '', 'BRAKE', ''], ['B8b', '', '', '' , '', 'NO', '', '', '', '', '', 'NO', ''], ['B8c', '', 'ALLOCATE', '' , '', 'BRAKE', '', 'STOP', '', 'SAFE', '', '', ''], ['B9b', 'STOP' , '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', 'BRAKE', ''], [ 'B9a', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP', '', 'SAFE', '' , '', ''], ['B10a', 'NO', '', 'NO', '', '', '', '', 'NO', '', '', '' , ''], ['B10b', 'STOP', '', 'SAFE', '', 'NO', '', '', 'ALLOCATE', '' , '', 'BRAKE', ''], ['B10c', '', 'ALLOCATE', '', '', 'BRAKE', '' , 'STOP', '', 'SAFE', '', '', ''], ['B11a', 'STOP', '', 'NO', '', '' , '', '', 'ALLOCATE', '', '', 'BRAKE', ''], ['B11b', '', 'ALLOCATE' , '', '', 'BRAKE', '', 'STOP', '', 'NO', '', '', ''], ['B11c', '', '' , '', '', '', '', '', '', '', '', '', ''], ['B12a', '', '', '', '' , '', '', '', '', '', '', '', ''], ['B12b', 'STOP', '', 'NO', '', '' , '', '', 'ALLOCATE', '', '', 'BRAKE', ''], ['B12c', '', 'ALLOCATE' , '', '', 'BRAKE', '', 'STOP', '', 'NO', '', '', ''], ['B13a', 'STOP' , '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', 'BRAKE', ''], [ 'B13b', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP', '', 'SAFE', '' , '', ''], ['B14a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE' , '', '', 'BRAKE', ''], ['B14b', '', 'ALLOCATE', '', '', 'BRAKE', '' , 'STOP', '', 'SAFE', '', '', ''], ['B15a', 'STOP', '', 'SAFE', '' , '', '', '', 'ALLOCATE', '', '', 'BRAKE', ''], ['B15b', '' , 'ALLOCATE', '', '', 'BRAKE', '', 'STOP', '', 'SAFE', '', '', ''], [ 'B16a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '' , 'BRAKE', ''], ['B16b', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP' , '', 'SAFE', '', '', ''], ['B17a', 'STOP', 'ALLOCATE', '', '30 MPH' , '', '', 'STOP', 'ALLOCATE', '', '30 MPH', '', ''], ['B18a', 'STOP' , 'ALLOCATE', '', '30 MPH', 'BRAKE', '', 'STOP', 'ALLOCATE', '', '30 ' 'MPH', 'BRAKE', ''], ['B19a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['B19b', '', '', '', '', 'BRAKE', '' , '', '', '', '', 'BRAKE', ''], ['B19c', '', 'ALLOCATE', '', '', '' , '', 'STOP', '', 'SAFE', '40 MPH', '', ''], ['B20a', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['B20b', '' , '', '', '', 'BRAKE', '', '', '', '', '40 MPH', '', ''], ['B20c', '' , '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B20d', '' , 'ALLOCATE', '', '40 MPH', '', '', 'STOP', '', 'SAFE', '', '', ''] , ['B21a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['B21b', '', '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE' , ''], ['B21c', '', 'ALLOCATE', '', '50 MPH', '', '', 'STOP', '' , 'SAFE', '', '', ''], ['B22a', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['B22b', '', '', '', '', 'BRAKE', '' , '', '', '', '', 'BRAKE', ''], ['B22c', '', 'ALLOCATE', '', '', '' , '', 'STOP', '', 'SAFE', '50 MPH', '', ''], ['B23a', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['B23b', '' , '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B23c', '' , 'ALLOCATE', '', '50 MPH', '', '', 'STOP', '', 'SAFE', '', '', ''] , ['B24a', 'STOP', '', 'NO', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['B24b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['B24c', '', '', 'SAFE', '', '', '', '', '', 'SAFE', '40 MPH', '' , ''], ['B24d', '', '', '', '40 MPH', '', '', '', '', '', '', 'BRAKE' , ''], ['B24e', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'NO', '' , '', ''], ['B25a', 'STOP', '', 'NO', '', '', '', '', 'NO', '', '' , '', ''], ['B25b', '', '', '', '', 'BRAKE', '', '', '', '', '', '' , ''], ['B25c', '', 'ALLOCATE', 'SAFE', 'Max.', '', '', '' , 'ALLOCATE', 'SAFE', '40 MPH', '', ''], ['B25d', '', '', '', '', '' , '', '', '', '', '', 'BRAKE', ''], ['B25e', '', 'NO', '', '40 MPH' , '', '', 'STOP', '', 'NO', '', '', ''], ['B26d', 'STOP', '', 'SAFE' , '', '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B26c', '', '' , '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B26b', '', '', '' , '', '', '', '', '', '', '', 'BRAKE', ''], ['B26a', '', 'ALLOCATE' , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B27d', 'STOP' , '', 'SAFE', '', '', '', '', 'ALLOCATE', '', 'Max.', '', ''], [ 'B27c', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B27b' , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B27a', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [ 'B28d', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['B28c', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''] , ['B28b', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [ 'B28a', '', 'ALLOCATE', '', 'Max.', '', '', 'STOP', '', 'SAFE', '' , '', ''], ['B29a', 'STOP', '', 'NO', '40 MPH', '', '', '' , 'ALLOCATE', '', '', '', ''], ['B29b', '', '', 'SAFE', '', 'BRAKE' , '', '', '', '', '', '', ''], ['B29c', '', '', '', '', '', '', '' , '', 'SAFE', '', 'BRAKE', ''], ['B29d', '', 'ALLOCATE', '', '', '' , '', 'STOP', '', 'NO', '', '', ''], ['B30a', 'STOP', '', 'SAFE', '' , '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B30b', '', '', '' , '', 'BRAKE', '', '', '', '', '', '', ''], ['B30c', '', '', '', '' , '', '', '', '', '', '', 'BRAKE', ''], ['B30d', '', 'ALLOCATE', '' , '30 MPH', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B31a', 'STOP' , '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], [ 'B31b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B31c' , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B31d', '' , 'ALLOCATE', '', '30 MPH', '', '', 'STOP', '', 'SAFE', '', '', ''] , ['B32a', 'NO', '', 'NO', '', '', '', '', 'NO', '', '', '', ''], [ 'B32b', 'STOP', '', 'SAFE', '', 'NO', '', '', 'ALLOCATE', '', '' , 'BRAKE', ''], ['B32c', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP' , '', 'SAFE', '', '', ''], ['B33a', 'STOP', 'ALLOCATE', '', '', '' , '', 'STOP', 'ALLOCATE', '', '50 MPH', '', ''], ['B34a', 'STOP' , 'ALLOCATE', '', '40 MPH', '', '', 'STOP', 'ALLOCATE', '', '', '' , '']], 1, 1, 0, 1.0, 0, 1, 0, ['Min.', '30 MPH', '40 MPH', '50 MPH' , 'Max.'], 2, 10, 0, 2, 2, [['Stop', -1, -1, 0], ['Clear', -1, -1, 5] , ['Diverging Clear Limited', -1, 1, 3], ['Approach', 0, 0, 2], [ 'Diverging Approach', 0, 1, 2], ['Approach Limited', 2, 0, 3], [ 'Diverging Approach Limited', 2, 1, 4]], 1, [['Single Head', [['Red'] , ['Green'], ['Yellow'], ['Flash-Green'], ['Flash-Yellow'], ['Green'] , ['Green']], [-1, -1, -1, -1, -1, -1, -1]], ['Double head', [['Red' , 'Red'], ['Green', 'Red'], ['Red', 'Flash-Green'], ['Yellow', 'Red'] , ['Red', 'Yellow'], ['Flash-Yellow', 'Red'], ['Red', 'Flash-Yellow'] ], [-1, -1, -1, -1, -1, -1, -1]]], [['HB03ccw', 'Double head', [ 'HB03ccw', 'HB03ccwL']], ['HB24cw', 'Double head', ['HB24cw' , 'HB24cwL']], ['HB19cw', 'Single Head', ['']], ['HB22ccw', 'Double ' 'head', ['HB22ccw', 'HB22ccwL']], ['HB08ccw', 'Single Head', ['']], [ 'HB10cw', 'Single Head', ['']], ['HB05cw', 'Double head', ['HB05cw' , 'HB05cwL']], ['HB27ccw', 'Double head', ['HB27ccw', 'HB27ccwL']], [ 'HB23cw', 'Double head', ['HB23cw', 'HB23cwL']], ['HB14ccw', 'Double ' 'head', ['HB14ccw', 'HB14ccwL']], ['HB18cw', 'Single Head', ['']], [ 'HB33ccw', 'Single Head', ['']], ['HB01ccw', 'Double head', [ 'HB01ccw', 'HB01ccwL']], ['HB04cw', 'Double head', ['HB04cw' , 'HB04cwL']], ['HB19ccw', 'Double head', ['HB19ccw', 'HB19ccwL']], [ 'HB20ccw', 'Single Head', ['']], ['HB06ccw', 'Single Head', ['']], [ 'HB22cw', 'Single Head', ['']], ['HB17cw', 'Single Head', ['']], [ 'HB25ccw', 'Double head', ['HB25ccw', 'HB25ccwL']], ['HB03cw' , 'Double head', ['HB03cw', 'HB03cwL']], ['HB12ccw', 'Single Head', [ '']], ['HB21cw', 'Double head', ['HB21cw', 'HB21cwL']], ['HB31ccw' , 'Double head', ['HB31ccw', 'HB31ccwL']], ['HB16cw', 'Single Head' , ['']], ['HB17ccw', 'Single Head', ['']], ['HB04ccw', 'Double head' , ['HB04ccw', 'HB04ccwL']], ['HB02cw', 'Single Head', ['']], [ 'HB34cw', 'Single Head', ['']], ['HB29cw', 'Double head', ['HB29cw' , 'HB29cwL']], ['HB23ccw', 'Single Head', ['']], ['HB09ccw', 'Single ' 'Head', ['']], ['HB20cw', 'Double head', ['HB20cw', 'HB20cwL']], [ 'HB15cw', 'Single Head', ['']], ['HB10ccw', 'Double head', ['HB10ccw' , 'HB10ccwL']], ['HB28ccw', 'Single Head', ['']], ['HB33cw', 'Single ' 'Head', ['']], ['HB01cw', 'Single Head', ['']], ['HB28cw', 'Double ' 'head', ['HB28cw', 'HB28cwL']], ['HB15ccw', 'Double head', ['HB15ccw' , 'HB15ccwL']], ['HB02ccw', 'Double head', ['HB02ccw', 'HB02ccwL']] , ['HB34ccw', 'Single Head', ['']], ['HB14cw', 'Single Head', ['']] , ['HB09cw', 'Double head', ['HB09cw', 'HB09cwL']], ['HB21ccw' , 'Single Head', ['']], ['HB07ccw', 'Single Head', ['']], ['HB32cw' , 'Single Head', ['']], ['HB27cw', 'Double head', ['HB27cw' , 'HB27cwL']], ['HB26ccw', 'Double head', ['HB26ccw', 'HB26ccwL']], [ 'HB13cw', 'Single Head', ['']], ['HB08cw', 'Double head', ['HB08cw' , 'HB08cwL']], ['HB13ccw', 'Double head', ['HB13ccw', 'HB13ccwL']], [ 'HB32ccw', 'Double head', ['HB32ccw', 'HB32ccwL']], ['HB31cw' , 'Double head', ['HB31cw', 'HB31cwL']], ['HB26cw', 'Single Head', [ '']], ['HB18ccw', 'Single Head', ['']], ['HB05ccw', 'Double head', [ 'HB05ccw', 'HB05ccwL']], ['HB12cw', 'Single Head', ['']], ['HB07cw' , 'Double head', ['HB07cw', 'HB07cwL']], ['HB24ccw', 'Double head', [ 'HB24ccw', 'HB24ccwL']], ['HB30cw', 'Double head', ['HB30cw' , 'HB30cwL']], ['HB25cw', 'Double head', ['HB25cw', 'HB25cwL']], [ 'HB11ccw', 'Single Head', ['']], ['HB29ccw', 'Double head', [ 'HB29ccw', 'HB29ccwL']], ['HB30ccw', 'Double head', ['HB30ccw' , 'HB30ccwL']], ['HB11cw', 'Single Head', ['']], ['HB16ccw', 'Double ' 'head', ['HB16ccw', 'HB16ccwL']], ['HB06cw', 'Double head', ['HB06cw' , 'HB06cwL']]], 0, 1, 1, 1, 60000, 1, 1, 1, 2000, 3, 0, [['Bell' , 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1] , '', 0.0, 0.0, 87.0, [], 1.0) exampleTrains["SignalMasts Example"] = [ ['T1017', 'B01', 'CCW', 0, '(3(B24 [B01 B02]) $P30 [B30 B31] [B01 ' 'B02])', 0, 650.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 , 10, 0, 1, 0, 13, 1, 0, 0, 0, 12], '1017', 0, [], 'Auto', 'B01', [1 , 2, 2, 3, 4], {}], ['T1019', 'B02', 'CCW', 0, '(3(B24 [B02 B01]) ' '$P30) ', 0, 680.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0 , 0, 2, 0, 1, 0, 0, 0, 1, 3, 0, 3], '1019', 0, [], 'Auto', 'B02', [1 , 2, 3, 4, 5], {}], ['T1633', 'B05', 'CW', 0, '(2(B20 $H:HB26ccw B24 ' '$R:HB26ccw [B05 B04 B03]) $P15 [B31 B30] $P10 [B05 B04 B03])', 0 , 650.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 14, 0, 1 , 0, 17, 1, 0, 0, 0, 16], '1633', 0, [], 'Auto', 'B05', [1, 2, 3, 4 , 5], {}], ['T1641', 'B30', 'CCW', 0, '([B02 B03 B01] B24 2([B03 B02 ' 'B01] B30) $P10)', 0, 410.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0 , 0, 1, 0, 0, 2, 0, 1, 0, 6, 1, 0, 0, 0, 4], '1641', 0, [], 'Auto' , 'B30', [1, 2, 3, 4, 5], {}], ['T3023', 'B31', 'CW', 0, '(2([B05 B04' ' B03] B31) $P25 [B05 B04 B03] B24)', 0, 490.0, [0, 1, 0, 0, 0, 0, 0 , 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1, 2, 1, 3, 0, 1, 0 , 7, 1, 0, 2, 1, 5], '3023', 0, [], 'Auto', 'B31', [1, 2, 3, 4, 5], { }], ['T3029', 'B06', 'CW', 0, '($SWOFF $CW 2(B24 [B04 B03]) [B31 B30]' ' [B04 B03] B20 $SWON $H:HB06cw $R:HB07cw $CCW B06 $D3 $OFF:F0)', 0 , 810.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 4, 0, 1 , 0, 0, 0, 1, 2, 0, 6, 0, 1, 0, 9, 1, 0, 2, 0, 8], '3029', 0, [] , 'Auto', 'B06', [1, 2, 3, 4, 5], {}], ['T4802', 'B07', 'CW', 0 , '($SWOFF $CW 3(B24 [B05 B04 B03]) B20 $SWON $H:HB07cw $R:HB06cw ' '$CCW B07 $D3 $OFF:F0)', 0, 250.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1 , 0, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0, 0, 1, 3, 0, 6, 0, 1, 0, 10, 1, 0 , 3, 0, 8], '4802', 1, [], 'Auto', 'B07', [1, 2, 3, 4, 5], {}], [ 'T4805', 'B32', 'CCW', 0, '($SWOFF $CCW [B02 B03 B01] B24 [B02 B03 ' 'B01] [B31 B30] $SWON $H:HB32ccw B29 $CW B32 $D3 $OFF:F0)', 0, 380.0 , [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 4, 0, 1, 0, 8 , 1, 0, 0, 1, 6], '4805', 0, [], 'Auto', 'B32', [1, 2, 3, 4, 5], {}]] examples["Xing Example"] = ( '2.0 Beta', ('CCW', 'CW'), 'N1', 'W', 1000, 0, 1, 0, 1, 2, 2, 30000 , 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA', 'CYAN'] , 1, [['E', '', 'CCW-CW', 'HEcw', 'HEccw', '', ''], ['N1', '', '' , 'HN1cw', 'HN1ccw', '', ''], ['N2', '', '', 'HN2cw', 'HN2ccw', '' , ''], ['NE', '', '', 'HNEcw', 'HNEccw', '', ''], ['NW', '', '' , 'HNWcw', 'HNWccw', '', ''], ['S1', '', '', 'HS1cw', 'HS1ccw', '' , ''], ['S2', '', '', 'HS2cw', 'HS2ccw', '', ''], ['SE', '', '' , 'HSEcw', 'HSEccw', '', ''], ['SW', '', '', 'HSWcw', 'HSWccw' , 'INVERTED', ''], ['W', '', 'CCW-CW', 'HWcw', 'HWccw', '', ''], [ 'Xa', '', 'CCW-CW', 'HXacw', 'HXaccw', '', ''], ['Xb', '', 'CCW-CW' , 'HXbcw', 'HXbccw', '', '']], [['Ed', 'STOP', '', 'SAFE', '', '', '' , '', 'ALLOCATE', '', '', '', ''], ['Ec', '', '', '', '', 'BRAKE', '' , '', '', '', '', '', ''], ['Eb', '', '', '', '', '', '', '', '', '' , '', 'BRAKE', ''], ['Ea', '', 'ALLOCATE', '', '', '', '', 'STOP', '' , 'SAFE', '', '', ''], ['N1d', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['N1c', '', '', '', '', 'BRAKE', '' , '', '', '', '', '', ''], ['N1b', '', '', '', '', '', '', '', '', '' , '', 'BRAKE', ''], ['N1a', '', 'ALLOCATE', '', '', '', '', 'STOP' , '', 'SAFE', '', '', ''], ['N2d', 'STOP', '', 'SAFE', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['N2c', '', '', '', '', 'BRAKE', '' , '', '', '', '', '', ''], ['N2b', '', '', '', '', '', '', '', '', '' , '', 'BRAKE', ''], ['N2a', '', 'ALLOCATE', '', '', '', '', 'STOP' , '', 'SAFE', '', '', ''], ['NEc', 'STOP', '', 'NO', '', '', '', '' , 'ALLOCATE', '', '', '', ''], ['NEb', '', '', '', '', 'BRAKE', '' , '', '', '', '', 'BRAKE', ''], ['NEa', '', 'ALLOCATE', '', '', '' , '', 'STOP', '', 'NO', '', '', ''], ['NWc', 'STOP', '', 'NO', '', '' , '', '', 'ALLOCATE', '', '', '', ''], ['NWb', '', '', '', '' , 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['NWa', '', 'ALLOCATE' , '', '', '', '', 'STOP', '', 'NO', '', '', ''], ['S1d', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['S1c', '', '' , '', '', 'BRAKE', '', '', '', '', '', '', ''], ['S1b', '', '', '' , '', '', '', '', '', '', '', 'BRAKE', ''], ['S1a', '', 'ALLOCATE' , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['S2d', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['S2c', '', '' , '', '', 'BRAKE', '', '', '', '', '', '', ''], ['S2b', '', '', '' , '', '', '', '', '', '', '', 'BRAKE', ''], ['S2a', '', 'ALLOCATE' , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['SEa', 'STOP', '' , 'NO', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['SEb', '', '' , '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['SEc', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'NO', '', '', ''], ['SWa' , 'STOP', '', 'NO', '', '', '', '', 'ALLOCATE', '', '', '', ''], [ 'SWb', '', '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], [ 'SWc', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'NO', '', '', ''] , ['Wd', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['Wc', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], [ 'Wb', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Wa', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['Xa' , 'STOP', 'ALLOCATE', '', '', '', '', 'STOP', 'ALLOCATE', '', '', '' , ''], ['Xb', 'STOP', 'ALLOCATE', '', '', '', '', 'STOP', 'ALLOCATE' , '', '', '', '']], 1, 1, 0, 25.4, 0, 1, 0, ['Min.', 'Low', 'Med.' , 'High', 'Max.'], 3, 10, 0, 2, 1, [['Stop', -1, -1, 0], ['Clear', -1 , -1, 5]], 1, [['Single Head', [['Red'], ['Green']], [-1, -1]]], [[ 'HXbccw', 'Single Head', ['HXbccw']], ['HWcw', 'Single Head', ['HWcw' ]], ['HEcw', 'Single Head', ['HEcw']], ['HS1cw', 'Single Head', [ 'HS1cw']], ['HSWcw', 'Single Head', ['HSWcw']], ['HSEcw', 'Single ' 'Head', ['HSEcw']], ['HNWccw', 'Single Head', ['HNWccw']], ['HSWccw' , 'Single Head', ['HSWccw']], ['HWccw', 'Single Head', ['HWccw']], [ 'HN1cw', 'Single Head', ['HN1cw']], ['HNWcw', 'Single Head', ['HNWcw' ]], ['HXacw', 'Single Head', ['HXacw']], ['HNEcw', 'Single Head', [ 'HNEcw']], ['HNEccw', 'Single Head', ['HNEccw']], ['HN1ccw', 'Single ' 'Head', ['HN1ccw']], ['HSEccw', 'Single Head', ['HSEccw']], ['HS2cw' , 'Single Head', ['HS2cw']], ['HEccw', 'Single Head', ['HEccw']], [ 'HS1ccw', 'Single Head', ['HS1ccw']], ['HN2ccw', 'Single Head', [ 'HN2ccw']], ['HS2ccw', 'Single Head', ['HS2ccw']], ['HN2cw', 'Single ' 'Head', ['HN2cw']], ['HXaccw', 'Single Head', ['HXaccw']], ['HXbcw' , 'Single Head', ['HXbcw']]], 0, 0, 0, 1, 20000, 0, 1, 1, 1778, 3, 0 , [['Bell', 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1] , '', 12.0, 0.0, 87.0, []) exampleTrains["Xing Example"] = [ ['T1017', 'S1', 'CCW', 0, '([N1 N2] [S1 S2] $P10)', 0, 889.0, [0, 1, 0 , 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 5, 1, 0, 0, 0 , 4], '1017', 0, [], 'Auto', 'S1', [1, 2, 3, 4, 5], {}], ['T1019', 'N1' , 'CW', 0, '([S1 S2] $P15 [N1 N2])', 0, 1143.0, [0, 1, 0, 0, 0, 0, 0 , 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 5, 1, 0, 0, 0, 4], '1019' , 0, [], 'Auto', 'N1', [1, 2, 3, 4, 5], {}], ['T1633', 'NE', 'CW', 0 , '(SE NW [S1 S2] [N1 N2] SW NE [S1 S2] [N1 N2] )', 0 , 355.6, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0 , 0, 14, 0, 1, 0, 17, 1, 0, 0, 0, 16], '1633', 0, [], 'Auto', 'NE', [ 1, 2, 3, 4, 5], {}], ['T1641', 'SW', 'CW', 0, '(SW SE)', 0, 203.2, [0 , 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 3], '1641', 0, [] , 'Auto', 'SW', [1, 2, 3, 4, 5], {}]] examples["Xover Example"] = ( '2.0 Beta', ('CCW', 'CW'), 'B1', 'B6', 1000, 0, 1, 1, 1, 2, 2, 30000 , 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA', 'CYAN'] , 1, [['B1', '', '', 'HB1ccw', 'HB1cw', '', ''], ['B2', '', '' , 'HB2ccw', 'HB2cw', '', ''], ['B3', '', '', 'HB3ccw', 'HB3cw', '' , ''], ['B4', '', '', 'HB4ccw', 'HB4cw', '', ''], ['B5', '' , 'CCW-CW+', 'HB5ccw', 'HB5cw', '', ''], ['B6', '', 'CCW-CW+' , 'HB6ccw', 'HB6cw', '', '']], [['B1a', 'STOP', '', 'SAFE', '', '' , '', '', 'ALLOCATE', '', '', '', ''], ['B1b', '', '', '', '' , 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B1c', '', 'ALLOCATE' , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B2a', 'STOP', '' , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['B2b', '', '' , '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B2c', '' , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B3a' , 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], [ 'B3b', '', '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], [ 'B3c', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', '' ], ['B4a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '' , ''], ['B4b', '', '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE' , ''], ['B4c', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '' , '', ''], ['B5a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '' , '', '', ''], ['B5b', '', '', '', '', 'BRAKE', '', '', '', '', '' , '', ''], ['B5c', '', '', '', '', '', '', '', '', '', '', 'BRAKE' , ''], ['B5d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '' , '', ''], ['B6a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '' , '', '', ''], ['B6b', '', '', '', '', 'BRAKE', '', '', '', '', '' , '', ''], ['B6c', '', '', '', '', '', '', '', '', '', '', 'BRAKE' , ''], ['B6d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '' , '', '']], 1, 1, 0, 25.4, 0, 1, 0, ['Min.', 'Low', 'Med.', 'High' , 'Max.'], 3, 10, 0, 2, 1, [['Stop', -1, -1, 0], ['Clear', -1, -1, 5] ], 1, [['Single Head', [['Red'], ['Green']], [-1, -1]]], [['HB2ccw' , 'Single Head', ['HB2ccw']], ['HB4cw', 'Single Head', ['HB4cw']], [ 'HB5ccw', 'Single Head', ['HB5ccw']], ['HB1ccw', 'Single Head', [ 'HB1ccw']], ['HB5cw', 'Single Head', ['HB5cw']], ['HB4ccw', 'Single ' 'Head', ['HB4ccw']], ['HB1cw', 'Single Head', ['HB1cw']], ['HB6cw' , 'Single Head', ['HB6cw']], ['HB3ccw', 'Single Head', ['HB3ccw']], [ 'HB2cw', 'Single Head', ['HB2cw']], ['HB3cw', 'Single Head', ['HB3cw' ]], ['HB6ccw', 'Single Head', ['HB6ccw']]], 0, 0, 0, 1, 60000, 0, 1 , 1, 2032, 3, 0, [['Bell', 'resources/sounds/bell.wav']], [1, 1, 1, 1 , 1, 1], '', 0.0, 0.0, 87.0, [], 1.0) exampleTrains["Xover Example"] = [ ['T1017', 'B1', 'CCW', 0, '(2([B3 B4] [B1 B2]) $P15)', 0 , 304.79999999999995, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0 , 0, 2, 0, 1, 0, 0, 0, 1, 2, 1, 3, 0, 1, 0, 6, 1, 0, 2, 1, 5], '1017' , 0, [], 'Auto', 'B1', [1, 2, 3, 4, 5], {}], ['T1019', 'B2', 'CW', 0 , '(2([B3 B4] [B1 B2]) $P20)', 0, 609.5999999999999, [0, 1, 0, 0, 0 , 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1, 2, 1, 3, 0 , 1, 0, 6, 1, 0, 2, 1, 5], '1019', 0, [], 'Auto', 'B2', [1, 2, 3, 4 , 5], {}]] # MAIN PROGRAMM ============== a = AutoDispatcher() a.setup()