Files
JIMRI/jython/AutoDispatcher2.py
T
2026-06-17 14:00:51 +02:00

10948 lines
467 KiB
Python

# 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, 2010, 2011
#
# 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.
# 2.36 - Avoided duplicate operation of turnouts due to changes in Layout Editor.
# 2.37 - Added section tracking using JMRI memory variables.
# 2.38 - Modified to ignored Power OFF when using XpressNet Simulator (since it's unreliable).
# 2.39 - Removed Operations interface (nobody ever used it!)
# 2.40 - Fixed thread race condition when stopping trains
# 2.41 - Set default minimum interval between speed commands (maxIdle) to 60 seconds
# 2.42 - Adapted to refactoring occurred in JMRI 3.32 (different access to default directory)
# 2.43 - timeout and retry on acquisition failure, handle new syntax of LayoutEditor class
# JAVA imports
import java
import java.awt
import java.awt.event
import java.beans
import java.io
import java.util
import jmri
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 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
# is this next import necessary?
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.getDefault(jmri.Timebase).getTime()
FastListener.fastTime = time.getHours() * 60 + time.getMinutes()
return
# MAIN CLASS ==============
class AutoDispatcher(jmri.jmrit.automat.AbstractAutomaton) :
# CONSTANTS ==========================
version = "2.43"
# 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.TurnoutType.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.getDefault(jmri.Timebase)
fastListener = FastListener()
# Status variables
error = False
loop = False
stopped = True
exiting = False
paused = False
repaint = False
simulation = False
lenzSimulation = 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.
# 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.getDefault().getFileLocation() +
baseName + "_AdP.bin")
self.trainFile = (Roster.getDefault().getFileLocation() +
baseName + "_AdT.bin")
# Locomotive settings are not based on panel title, since there is
# only one JMRI roster
self.locoFile = (Roster.getDefault().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.find(".LayoutEditor") > 0) :
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.getDefault(SignalHeadManager).getNamedBeanSet()
AutoDispatcher.signalHeadNames = [""]
for s in signalHeads :
# Use UserName (if available), otherwise SystemName
signalName = s.getUserName()
if signalName == None or signalName.strip() == "" :
signalName = s.getSystemName()
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
# (it's unfortunate that this uses "sections" differently than the jmri_defaults style)
sections = InstanceManager.getDefault(jmri.SectionManager).getNamedBeanSet()
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.getSystemName())
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.getDefault().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.getDefault(jmri.ConsistManager)
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.getDefault(jmrix.SignalHeadManager).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
# if not AutoDispatcher.paused :
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.currentSpeed, 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 = locomotive.currentSpeed = 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.currentSpeed = 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.currentSpeed = 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.currentSpeed = 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.getDefault(jmri.SectionManager).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.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager).getLayoutBlock(blockName)
if layoutBlock == None :
blockName = newBlock.getSystemName()
layoutBlock = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager).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.memoryVariable = jmri.InstanceManager.memoryManagerInstance().getMemory(self.name)
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.getDefault(jmri.BlockManager).getNamedBeanSet()
blockSensors = []
for b in blocks :
sensor = b.getSensor()
if sensor != None :
blockSensors.append(sensor)
sensors = InstanceManager.sensorManagerInstance().getNamedBeanSet()
for s in sensors:
if not s in blockSensors :
sensorName = 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 Memory Variable (if defined)
# and in jmri Blocks (only if user enabled this option)
if self.memoryVariable != None :
if self.allocated == None :
self.memoryVariable.setValue("")
else :
self.memoryVariable.setValue(self.allocated.getName())
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, turnoutList) :
# 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()
# Make sure turnout is not included twice in paths
# Seems to happen starting with JMRI 2.11.4
if not turnout in turnoutList :
turnoutList.append(turnout)
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.currentSpeed = 0
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(None)
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
# request address, timeout set to 5 seconds
self.throttle = AutoDispatcher.instance.getThrottle(self.address, long, 5)
if (self.throttle == None) :
AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
"Couldn't assign throttle " + str(self.address) + "!")
self.throttleAssigned = False
else :
if self.leadLoco != 0 :
AutoDispatcher.message("Acquired throttle for consist "
+ self.name + " (" + str(self.address) + ")")
self.leadThrottle = AutoDispatcher.instance.getThrottle(
self.leadLoco, long, 5)
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 + "!")
self.throttleAssigned = False
else :
AutoDispatcher.message("Acquired throttle for locomotive "
+ self.name + " (" + str(self.address) + ")")
self.leadThrottle = self.throttle
speedStepMode = self.throttle.getSpeedStepMode()
if speedStepMode == SpeedStepMode.NMRA_DCC_128 :
self.stepsNumber = 126.
elif speedStepMode == SpeedStepMode.NMRA_DCC_28 :
self.stepsNumber = 28.
elif speedStepMode == SpeedStepMode.NMRA_DCC_27 :
self.stepsNumber = 27.
else :
self.stepsNumber = 14.
self.currentSpeed = self.throttle.getSpeedSetting()
def releaseThrottle(self) :
if self.throttle != None :
if ADsettings.lightMode != 0 :
self.setFunction(0, False)
self.throttle.release(None)
self.throttle = None
self.throttleAssigned = False
if self.leadLoco != 0 :
self.leadThrottle.release(None)
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 = self.currentSpeed = 0
# self.targetSpeed = 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 = []
self.turnoutList = []
# 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, self.turnoutList) :
thrown = True
# and vice-versa
if endBlock.setTurnouts(startBlock, self.turnoutList) :
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, self.turnoutList) :
thrown = True
if previousBlock.setTurnouts(block, self.turnoutList) :
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.getDefault(SignalHeadManager).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 = 60000
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)
try :
soundRoot = jmri.util.FileUtil.getUserFilesPath()
except :
try:
soundRoot = XmlFile.userFileLocationDefault()
except :
AutoDispatcher.log("Unable to find user sound's directory")
soundRoot = ""
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.getDefault(jmri.PowerManager)
if self.powerManager != None and not AutoDispatcher.lenzSimulation :
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 and not AutoDispatcher.lenzSimulation :
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.setLocation(0, 0)
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)-3]
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):
trainIds = []
# 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 = 0
# 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()