JMR2.4 - John's Model Railway
A.J.Hurst
Version 2.4.3
20150829:162117
Table of Contents
1. Introduction
This is a suite of programs to drive John's Model Railway
(version 2), aka JMR2. It is a rewrite of the version 1 code,
to operate from the fat-controller computer system.
Version 1 was itself a rewrite from earlier version 0. More
details to follow.
Version 2.1 restructures much of the code into more coherent
modules, so that both the server programs (trains.py and
jmr.py) use common code where possible.
Version 2.2 attempts to rationalize the point and signal
interconnections so that a more systematic naming and
operational convention applies.
Version 2.3 removes a lot of the signal and point route
setting/conflict logic from the svgLayout.py module, and
places it instead into the jmr.py and
interlocking.py modules.
Version 2.4 restructures most of the layout components into a
module called LayoutComponents, written in a more OO
style.
1.1 What this suite provides
This suite of code defines all the constants, modules,
programs, and server programs used to control John's Model
Railway. It is written as a literate program.
There are two server programs and two Arduino programs that
comprise the system. The main server program provides a
public web interface that delivers an updateable layout
schematic that not only enables the reader to see the current
state of the signals and points throughout the system, but
also provides control of the system through mouse clickable
representations of the points and signals. This server does
not interface directly to the layout, but instead sends http
requests to a local server (which is not publically
available).
This local server interfaces directly to two
Arduino systems via USB connections, which drive the points
and signal circuitry respectively. This is via an I2C bus
connecting to a fan-out of driver transistors, one for each
point motor and signal lamp. (The points circuitry actually
uses a cross-bar mechanism to reduce the number of direct
transistor drivers.)
The local server is designed to be compatible with the code
used in the main server. The main differences between the two
servers are that the local server has no responsibility for
displaying the track schematic, while the main server has no
direct connection to the Arduino sub-systems. The reasons for
this are partly historical and partly technical, but the use
of a two-layer system also allows for an additional level of
security in that public users cannot interfere directly with
the track control circuitry..
2. The Data Structures
2.1 Some definitions
- Aspects
- The colour shown by a signal. Besides the obvious
colours of R (red), Y (yellow) and G (green), there is also
X (unknown) and B (blue, or bad).
- Signals
- Each signal is known by three components: an area code
(C,G,M,P,S for Cabernet, Grenache, Merlot, Pinot and Shiraz
respectively), the signal type indicator 's', and a cardinal
number to distinguish within the area. This composite is
called a signal name, and an example is Ms04.
There is a data structure signals which is a
dictionary from signal names to aspects: {signal names =>
aspects}. A composite of signal name and aspect is called a
signal aspect, such as Ms04G (signal named
Ms4 is showing a green aspect).
- Directions
- The direction in which a point lies. It can be either L
(left) or R (right), and in the case of three-way points, C
(centre). There is also the unknown state X.
- Points
- Each point is known by three components: an area code
(C,G,M,P,S for Cabernet, Grenache, Merlot, Pinot and Shiraz
respectively), the point type indicator 'p', and a cardinal
number to distinguish within the area. This composite is
called a point name, and an example is Gp02.
There is a data structure points which is a
dictionary from point names to directions: {point names =>
directions}. A composite of point name and direction is
called a point direction, such as Gp02L (point
named Gp02 is thrown to the left direction).
Each point has an orientation, indicating which
way the toe is facing. This is 'C' for clockwise trains,
and 'A' for anticlockwise trains. Note that for the inner
tracks, the orientation has to be carefully defined,
particularly where reversing loops occur. (More on this
later.)
- Track Segment
- A track segment is a piece of track that is controlled
as a unit. It has a state which may be "unknown"
('X'), "set" ('S') or "clear" ('C').
If the state of a track segment is unknown, it may mean
exactly that, or it may indicate that it is specifically not
set and not clear. In either form, it is not suitable for a
train to be travelling over that track segment, as the
safety of the train cannot be guaranteed. Track segments
are named in two ways:
- As part of the toe of a point, when it is labelled
with a point direction, such as Cp02L.
- As an independent piece of track, when it is
labelled with an area code, component type 't',
and a cardinal number. An example is 'Ct03'.
- Blocks
- A block is a key component in the signalling of
the layout. Each signal controls entry to one or more
blocks, where each block is a sequence of contiguous track
segments to the next (facing) signal. The status of a block
reflects the status of the track segments from which it is
composed. If one or more track segments is unknown, the
state of the block is unknown. If all the track segments in
the block are set, the block is set. If all the track
segments are clear, the block is clear. (It is normally an
error if some track segments in a block are clear and some
are set.) Note that blocks are defined in the
signalControls data table, which is a mapping from signal
names to a list of blocks: {signal names => Blocks}
- Paths
- svgdom elements
- route
- [path names]
- Routes
- [[path names]]
- section
- A section is a list of path names that may be cleared on
the setting of a signal. Note that sections may have forks
in them where they include facing points. It is up to the
display logic to show which particular route in a section is
cleared.
- Sections
- [[path names]]
2.2 Basic JMR Data
"jmrData.py" 2.1 =
This module defines constants used throughout the suite of
programs.
- regionPat
- defines a regular expression to match region
designations.
- numberPat
- defines a regular expression to match the nominal
number of the signal or point designation.
- directionPat
- defines a regular expression to match the direction
setting of a point.
2.3 The Persistent Data
A number of files are used to store persistent data
associated with the layout. A brief overview of the role of
these files is given here, and more information appears within
each definition.
- pointsData.py
- Define the state of each set of points in the layout.
This is a simple dictionary structure, mapping point names to
directions.
- signalsData.py
- Define the state of each signal in the layout. This is
a mapping from signal names to aspects.
- signalControls.py
- This table identifies the routes controlled by each
signal. It takes the form of a dictionary, indexed by
signal name, into a list of blocks for
each signal. A block is defined as a list of track segments
(q.v.).
- blocksData.py
- A table of the blocks controlled by each signal. Since
these are directional, and each signal can control more than
one block, each block is identified by a pair a contiguous
facing signals, such as Cs7-Ps2.
2.3.1 The Layout Input Language (not yet used in 2.4)
An input language for describing the layout has been
developed. This consists of two parts: the points definition,
and the signals, or block definitions. Here is the BNF
definition of the input language: (see also the Support
Programs section)
Line = (Signal '-' ControlList) | (Point '-' AlternatePath) .
ConflictLine = Signal '-' AlternatePath
Signal = Area 's' Number .
Area = 'C' | 'G' | 'M' | 'P' | 'S' .
Number = ('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9')+ .
Track = Area 't' Number .
ControlList = Path [',' Path] .
Path = Signal | PointPath | Track | AlternatePath .
PointPath = Area 'p' Number [ Dir ] .
Dir = 'L' | 'R' .
AlternatePath = '(' Guard [',' ControlList] ')' ['/' AlternatePath] .
Guard = PointPath .
2.3.2 The signals table (updated 20150820:182224)
"signalsData.py" 2.2 ={
# key:[aspect,controls,orientation]
'Cs01':['R',['Cp03L'],'A'],
'Cs02':['R',['Cp02L'],'A'],
'Cs03':['R',['Ct07'],'A'],
'Cs04':['R',['Ct04'],'C'],
'Cs05':['R',['Cp01L'],'C'],
'Cs06':['R',['Cp05L'],'C'],
'Cs07':['R',['Ct02'],'A'],
'Cs08':['R',['Cp04R'],'C'],
'Gs01':['R',['Gt01'],'A'],
'Gs02':['R',['Gp05R'],'A'],
'Gs03':['R',['Gp01R'],'C'],
'Gs04':['R',['Gp02R'],'C'],
'Gs05':['R',['Gt04'],'C'],
'Gs06':['R',['Gp03R'],'A'],
'Ms01':['R',['Mt05'],'C'],
'Ms02':['R',['Mt10'],'A'],
'Ms04':['R',['Mt02'],'C'],
'Ps01':['R',['Pt01'],'C'],
'Ps02':['R',['Mt08'],'A'],
'Ps03':['R',['Mt06'],'A'],
'Ps04':['R',['Pp56C'],'C'],
'Ps05':['R',['Pt06'],'A'],
'Ps06':['R',['Pp09R'],'A'],
'Ps07':['R',['Pt08'],'C'],
'Ps08':['R',['Pt05'],'C'],
'Ps09':['R',['Pp10R'],'A'],
'Ps10':['R',['Pp03L'],'C'],
'Ss01':['R',['St04'],'C'],
'Ss02':['R',['Sp02L'],'C'],
'Ss03':['R',['St03'],'A'],
'Ss04':['R',['St05'],'A'],
'Ss05':['R',['St08'],'C'],
'Ss06':['R',['Sp06L'],'C'],
'Ss09':['R',['St09'],'A'],
'Ss10':['R',['Sp13L'],'A'],
}
Each signal is given a name, consisting of its region prefix
(C/G/M/P/S),
followed by the letter 's', followed by a
nominal number unique within the region. The table is a
dictionary indexed by the signal name, with value a list of
attributes. The first attribute is the aspect of the
signal. The second attribute is list of track segments
controlled by the signal (usually only one segment), and the
third is the orientation of the points, with 'C' indicating
that the toe faces clockwise trains, and 'A' indicating that
the toe faces anticlockwise trains.
Note that the
aspect of the signal is drawn from the character set
R (for Red), G (for Green), or
B (for Blue). The latter is not an aspect
shown by the signal, but is used to indicate where a signal
is in conflict with the current route setting. A Blue
signal must be reset to Red before it can be cleared.
2.3.3 The points table (updated 20150821:111101)
"pointsData.py" 2.3 ={
# key:[throw,orientation,[facing segments],[left segments],[right segments]
'Cp01':['L','A',['Cp02L', 'Cp02R'],['Ct01'],['Cp03R']],
'Cp02':['L','C',['Cp01L', 'Cp01R'],['Ct03'],['Cp05R']],
'Cp03':['L','A',['Ct04'],['Gt02'],['Cp01R']],
'Cp04':['R','A',['Ct02'],['Ct08'],['Ct08']],
'Cp05':['R','A',['Ct07'],['Mt09'],['Cp02R']],
'Gp01':['L','A',['St02'],['Gp05L'],['Gt02']],
'Gp02':['R','A',['Gp05L', 'Gp05R'],['Gp03L'],['Gt03']],
'Gp03':['R','C',['Gp04L'],['Gp02L'],['Mt01']],
'Gp04':['R','C',['Gt04'],['Gp03L', 'Gp03R'],['Pt05']],
'Gp05':['R','C',['Gp02L', 'Gp02R'],['Gp01L'],['St01']],
'Mp01':['L','C',['Mt05'],['Mp02R'],['Mp03L']],
'Mp02':['R','A',['Mt10'],['Mp03R'],['Mp01L']],
'Mp03':['L','C',['Mt03'],['Mp01R'],['Mp02L']],
'Mp04':['R','A',['Mt03'],['Mp05R'],['Mp06L']],
'Mp05':['L','A',['Mt04'],['Mp06R'],['Mp04L']],
'Mp06':['L','C',['Mt02'],['Mp04R'],['Mp05L']],
'Pp01':['R','C',['Pt01'],['Mt07'],['Pt02']],
'Pp02':['R','C',['Pt02'],['Pp04L'],['Pp07L']],
'Pp03':['L','A',['Pp04L', 'Pp04R'],['Pt03'],['Pp56L', 'Pp56C', 'Pp56R']],
'Pp04':['R','C',['Pp03L', 'Pp03R'],['Pp02L'],['Mt08']],
'Pp07':['L','A',['Pt06'],['Pp56R'],['Pp02R']],
'Pp08':['R','A',['Pp04L', 'Pp04R'],[],['Pp11L', 'Pp11R']],
'Pp09':['L','C',['Pt05'],['Pp10L'],['Pt04']],
'Pp10':['R','C',['Pt08'],['Pp09L'],['Pt09']],
'Pp11':['L','A',['Pp08R'],[],[]],
'Pp56':['C','A',['Pp03R'],['Pp08L', 'Pp08R'],['Pp07R']],
'Sp01':['L','C',['St04'],['Sp03L'],['Sp02R']],
'Sp02':['L','A',['St03'],['St01'],['Sp01R']],
'Sp03':['L','C',['St05'],['Sp01L'],[]],
'Sp04':['R','A',['Sp05R'],[],[]],
'Sp05':['L','A',['Sp06R'],[],['Sp04L', 'Sp04R']],
'Sp06':['L','A',['Sp07L', 'Sp07R'],['St07'],['Sp05L', 'Sp05R']],
'Sp07':['R','C',['Sp06L', 'Sp06R'],['Sp10L', 'Sp10R'],['Sp12R']],
'Sp08':['R','C',['St08'],['Sp09L'],['Sp14L', 'Sp14R']],
'Sp09':['L','C',['Sp12L'],['Sp08L'],[]],
'Sp10':['L','C',['Sp07L'],['Sp11L', 'Sp11R'],[]],
'Sp11':['L','C',['Sp10L'],[],[]],
'Sp12':['R','A',['Sp13L', 'Sp13R'],['Sp09L', 'Sp09R'],['Sp07R']],
'Sp13':['L','C',['Sp12L', 'Sp12R'],['St11'],['Sp15R']],
'Sp14':['L','A',['Sp08R'],['Sp15L'],[]],
'Sp15':['L','A',['St09'],['Sp14L'],['Sp13R']],
}
Each point is given a name, consisting of its region prefix
(C/G/M/P/S),
followed by the letter 'p', followed by a nominal
number unique within the region. The table consists of a
dictionary indexed by the point name, with value a list of
attributes.
There are normally 5 attributes, but in the case of 3-way
points, there is a 6th attribute. These are:
-
The throw of the point: 'L' (left); 'R' (right); or 'C'
(centre).
-
The orientation of the point: 'C' (clockwise); or 'A'
(anticlockwise).
-
A list of track segments on the left throw of the points.
-
A list of track segments on the right throw of the points.
-
A list of track segments facing the points.
-
A list of track segments on the centre throw of the points.
(currently if the 6th attribute is present, it appears as
the 4th element, but this is to be restructured.)
The throw is determined by looking at the
point from the facing heel (switch blade) end, towards
the frog or toe (as opposed to looking at the heel from
the toe, or frog end). The heel end is also called the
facing end, and the toe end the trailing
end. A good way of remembering this is to note that the
human foot has a narrow or heel end, and a wide or toe end
just like points.
Double slips are regarded as two back-to-back points, even
though the actual heels are reversed, such that the facing
heel of the direction of travel is encountered first
(whereas for back-to-back points, the facing heel in the
direction of travel is encountered last).
Three-way points are labelled as a pair of points, although
only 3 of the four possible settings are permitted. This is
indicated by the possible directions being L,
R and C (centre). There is only one three-way
point in this layout: Pp56.
The track segment lists may be empty (a buffer stop
segment, although unusual for points), a singleton (the
usual case), or a pair of segments (this throw leads to
another set of points). A triple is also possible in the
case of 3-way points.
The route selected by the current setting of the point is
shown on the schematic with a green track. The non-selected
branch is shown black. If setting a point is in conflict with
current route settings, the selected route is shown on the
schematic as blue.
2.3.4 The track segment table (updated 20150821:111209)
"trackData.py" 2.4 ={
# key:[status,[clockwise],[anticlockwise]]
'Cp01L':['C',['Cp02L', 'Cp02R'],['Ct01']],
'Cp01R':['X',['Cp02L', 'Cp02R'],['Cp03R']],
'Cp02L':['C',['Ct03'],['Cp01L', 'Cp01R']],
'Cp02R':['X',['Cp05R'],['Cp01L', 'Cp01R']],
'Cp03L':['S',['Gt02'],['Ct04']],
'Cp03R':['X',['Cp01R'],['Ct04']],
'Cp04L':['C',['Ct02'],['Ct08']],
'Cp04R':['X',['Ct02'],['Ct06']],
'Cp05L':['S',['Ct07'],['Mt09']],
'Cp05R':['X',['Ct07'],['Cp02R']],
'Ct01':['C',['Cp01L'],['Ct02']],
'Ct02':['C',['Ct01'],['Cp04L', 'Cp04R']],
'Ct03':['C',['Gt03'],['Cp02L']],
'Ct04':['S',['Cp03L', 'Cp03R'],['Ct05']],
'Ct05':['S',['Ct04'],['Mt10']],
'Ct06':['S',['Cp04R'],['Mt08']],
'Ct07':['S',['Gt04'],['Cp05L', 'Cp05R']],
'Ct08':['C',['Cp04L'],['Mt06']],
'Gp01L':['X',['Gt01'],['Gp05L']],
'Gp01R':['S',['Gt01'],['Gt02']],
'Gp02L':['X',['Gp05L', 'Gp05R'],['Gp03L']],
'Gp02R':['C',['Gp05L', 'Gp05R'],['Gt03']],
'Gp03L':['S',['Gp02L'],['Gp04L']],
'Gp03R':['S',['Mt01'],['Gp04L']],
'Gp04L':['S',['Gp03L', 'Gp03R'],['Gt04']],
'Gp04R':['X',['Pt05'],['Gt04']],
'Gp05L':['X',['Gp01L'],['Gp02L', 'Gp02R']],
'Gp05R':['C',['St01'],['Gp02L', 'Gp02R']],
'Gt01':['S',['St02'],['Gp01L', 'Gp01R']],
'Gt02':['S',['Gp01R'],['Cp03L']],
'Gt03':['C',['Gp02R'],['Ct03']],
'Gt04':['S',['Gp04L', 'Gp04R'],['Ct07']],
'Mp01L':['X',['Mp02R'],['Mt05']],
'Mp01R':['S',['Mp03L'],['Mt05']],
'Mp02L':['X',['Mt10'],['Mp03R']],
'Mp02R':['S',['Mt10'],['Mp01L']],
'Mp03L':['S',['Mt03'],['Mp01R']],
'Mp03R':['X',['Mp02L'],['Mt03']],
'Mp04L':['X',['Mp05R'],['Mt03']],
'Mp04R':['S',['Mt03'],['Mp06L']],
'Mp05L':['S',['Mt04'],['Mp06R']],
'Mp05R':['X',['Mt04'],['Mp04L']],
'Mp06L':['S',['Mp04R'],['Mt02']],
'Mp06R':['X',['Mp05L'],['Mt02']],
'Mt01':['S',['Mt02'],['Gp03R']],
'Mt02':['S',['Mp06L', 'Mp06R'],['Mt01']],
'Mt03':['S',['Mp03L', 'Mp03R'],['Mp04L', 'Mp04R']],
'Mt04':['S',['Mt09'],['Mp05L', 'Mp05R']],
'Mt05':['S',['Mp01L', 'Mp01R'],['St11']],
'Mt06':['C',['Ct08'],['Mt07']],
'Mt07':['C',['Mt06'],['Pp01L']],
'Mt08':['S',['Ct06'],['Pp04R']],
'Mt09':['S',['Cp05L'],['Mt04']],
'Mt10':['S',['Ct05'],['Mp02L', 'Mp02R']],
'Pp01L':['C',['Mt07'],['Pt01']],
'Pp01R':['X',['Pt02'],['Pt01']],
'Pp02L':['X',['Pp04L'],['Pt02']],
'Pp02R':['S',['Pp07L'],['Pt02']],
'Pp03L':['X',['Pp04L', 'Pp04R'],['Pt03']],
'Pp03R':['S',['Pp04L', 'Pp04R'],['Pp56L', 'Pp56C', 'Pp56R']],
'Pp04L':['X',['Pp02L'],['Pp03L', 'Pp03R']],
'Pp04R':['S',['Mt08'],['Pp03L', 'Pp03R']],
'Pp07L':['X',['Pt06'],['Pp02R']],
'Pp07R':['S',['Pt06'],['Pp56R']],
'Pp08L':['X',['Pp56L'],[]],
'Pp08R':['S',['Pp56L'],['Pp11L', 'Pp11R']],
'Pp09L':['S',['Pp10L'],['Pt05']],
'Pp09R':['X',['Pt04'],['Pt05']],
'Pp10L':['X',['Pp09L'],['Pt08']],
'Pp10R':['S',['Pt09'],['Pt08']],
'Pp11L':['S',['Pp08R'],[]],
'Pp11R':['S',['Pp08R'],[]],
'Pp56C':['X',['Pp03R'],['Pt04']],
'Pp56L':['X',['Pp03R'],['Pp08L', 'Pp08R']],
'Pp56R':['S',['Pp03R'],['Pp07R']],
'Pt01':['C',['Pp01L', 'Pp01R'],['St10']],
'Pt02':['S',['Pp02L', 'Pp02R'],['Pp01R']],
'Pt03':['S',['Pp03L'],['Pt09']],
'Pt04':['S',['Pp56C'],['Pp09R']],
'Pt05':['S',['Pp09L', 'Pp09R'],['Gp04R']],
'Pt06':['S',['Pt07'],['Pp07L', 'Pp07R']],
'Pt07':['S',['Pt08'],['Pt06']],
'Pt08':['S',['Pp10L', 'Pp10R'],['Pt07']],
'Pt09':['S',['Pt03'],['Pp10R']],
'Sp01L':['S',['Sp03L'],['St04']],
'Sp01R':['X',['Sp02R'],['St04']],
'Sp02L':['C',['St03'],['St01']],
'Sp02R':['S',['St03'],['Sp01R']],
'Sp03L':['S',['St05'],['Sp01L']],
'Sp03R':['X',['St05'],[]],
'Sp04L':['S',['Sp05R'],[]],
'Sp04R':['S',['Sp05R'],[]],
'Sp05L':['S',['Sp06R'],[]],
'Sp05R':['S',['Sp06R'],['Sp04L', 'Sp04R']],
'Sp06L':['S',['Sp07L', 'Sp07R'],['St07']],
'Sp06R':['X',['Sp07L', 'Sp07R'],['Sp05L', 'Sp05R']],
'Sp07L':['X',['Sp10L', 'Sp10R'],['Sp06L', 'Sp06R']],
'Sp07R':['S',['Sp12R'],['Sp06L', 'Sp06R']],
'Sp08L':['X',['Sp09L'],['St08']],
'Sp08R':['C',['Sp14L', 'Sp14R'],['St08']],
'Sp09L':['S',['Sp12L'],['Sp08L']],
'Sp09R':['X',[],[]],
'Sp10L':['S',['Sp11L', 'Sp11R'],[]],
'Sp10R':['X',[],['Sp07L']],
'Sp11L':['S',[],['Sp10L']],
'Sp11R':['X',[],['Sp10L']],
'Sp12L':['X',['Sp13L', 'Sp13R'],['Sp09L', 'Sp09R']],
'Sp12R':['S',['Sp13L', 'Sp13R'],['Sp07R']],
'Sp13L':['S',['St11'],['Sp12L', 'Sp12R']],
'Sp13R':['X',['Sp15R'],['Sp12L', 'Sp12R']],
'Sp14L':['C',['Sp15L'],['Sp08R']],
'Sp14R':['X',[],['Sp08R']],
'Sp15L':['C',['St09'],['Sp14L']],
'Sp15R':['X',['St09'],['Sp13R']],
'St01':['C',['Sp02L'],['Gp05R']],
'St02':['S',['St04'],['Gt01']],
'St03':['C',['St06'],['Sp02L', 'Sp02R']],
'St04':['S',['Sp01R', 'Sp01L'],['St02']],
'St05':['S',['St07'],['Sp03R', 'Sp03L']],
'St06':['C',['St08'],['St03']],
'St07':['S',['Sp06L'],['St05']],
'St08':['C',['Sp08L', 'Sp08R'],['St06']],
'St09':['C',['St10'],['Sp15L', 'Sp15R']],
'St10':['C',['Pt01'],['St09']],
'St11':['S',['Mt05'],['Sp13L']],
}
Each track segment is given a name, consisting of its region
prefix
(C/G/M/P/S),
followed by the letter 't', followed by a
nominal number unique within the region. The table consists
of a dictionary indexed by the track name, with the value
being a list of attributes.
There are 3 attributes:
-
The status of the segment: 'X' (unknown); 'S' (set); 'C'
(clear); and 'O' (occupied). A 'set' track means that
the points are aligned for this track segment, but the
controlling signals are not clear. The 'clear' status
indicates that controlling signals are green (or
yellow).
-
A list of track segments following in the clockwise direction.
-
A list of track segments following in the anticlockwise
direction.
2.3.5 Signal Control Definitions
This file is generated by the makeJmrData.py program (q.v.)
The file defines the pathways, known as blocks, controlled by
each signal. Each block is a list of track segments from one
facing signal to the next facing signal. A given signal may
control more than one block. A track segment is a contiguous
piece of track, which may be "set" or "cleared".
Setting a track segment means that the track segment is added
to a block being built and made ready for clearing. Once a
complete block is set, it may be cleared by setting the
controlling signal to green. (Note that a signal may not be
cleared if it does not have a set block that it controls.) A
clear track segment is part of a cleared block.
Track segments are of two types: point controlled segments,
and standalone segments. A point controlled segment is set by
the process of toggling the points to enable a route through
the given segment, that is, the points are thrown in the
direction of the segment. Standalone segments are set
whenever the segments at either end are set. A standalone
segment at the heel of the set of points (the common path
through the points) is set whenever the points are thrown in
either direction.
It follows that the only time that a heel segment is not set
or cleared is when the the entire layout is reset, that is, no
information about the state of points is known, and all signals
are red.
2.3.6 The conflicts table
The conflicts table defines those aspects of signals and
directions of points (subsequently called just "aspects") that
would preclude the clearing of a signal (setting to green), or
the changing of a point.
Each signal or point is defined by its name, and the name is
used as index into a dictionary of lists (called the "conflict
list" for that particular signal or point). Each element of
the the conflict list is itself a list (called the "conflict
pathway"), defining one set of aspect settings that identifies
a conflict. If any conflict pathway indicates a conflict,
then the entire conflict list identifies a conflict - that is,
the overall conflict is the OR of each conflict pathway.
A conflict pathway identifies one or more points or
signals. Only when all the listed points/signals have the given
aspect is a conflict defined - that is, the overall conflict
of a conflict pathway is the AND of all the indicated points
and signals.
The aspects of the given pathway points/signals are indicated
by a list (the conflict pathway list) of pairs of firstly
point/signal names, followed by the aspect. Note that a red
signal can never cause a conflict, so where signals are
indicated as the first of a pair, the second component must
always be 'G'.
This file is now generated by the makeJmrData.py program.
2.3.7 The Routes Definition
The routes data table defines the signals and points settings
require to set certain predefined routes. The routes are
defined with single names, while routes and directions
settings are defined with pairs of names. The first group set
just points, while the second group set both points and
signals.
"routesData.py" 2.5 ={
'inner' : [('Gp02','R'),('Gp05','R'),
('Cp01','L'),('Cp02','L'),('Cp04','L'),
('Pp01','L'),
('Sp15','L'),('Sp14','L'),('Sp08','R'),('Sp02','L')],
'inner2pinot' : [('Gp02','R'),('Gp05','R'),
('Cp01','L'),('Cp02','L'),('Cp04','R'),
('Pp01','R'),('Pp02','R'),('Pp03','L'),('Pp04','R'),
('Pp10','R'),('Pp07','L'),('Sp15','L'),('Sp14','L'),('Sp08','R'),('Sp02','L')],
'outer' : [('Gp01','R'),('Cp03','L'),('Mp02','R'),('Mp01','L'),
('Sp13','L'),('Sp12','R'),('Sp07','R'),('Sp06','L'),('Sp03','L'),('Sp01','L')],
'upper' : [('Cp05','L'),('Mp05','L'),('Mp06','R'),('Gp03','R'),('Gp04','L')],
'inner,clock': ['Gs02','Cs02','Cs07','Ps03','Ss09','Ss03','Cs09',
'Cs05','Gs04','Ss02','Ss05','Ps01'],
'inner,anti' : ['Cs09','Cs05','Gs04','Ss02','Ss05','Ps01','Gs02',
'Cs02','Cs07','Ps03','Ss09','Ss03'],
'outer,clock': ['Gs01','Cs01','Ms02','Cs04','Gs03','Ms01','Ss06','Ss01'],
'outer,anti' : ['Cs04','Gs03','Ms01','Gs01','Cs01','Ms02','Ss10','Ss04'],
'upper,clock': ['Gs06','Cs03','Ms03','Gs05','Ms04','Cs06'],
'upper,anti' : ['Gs05','Ms04','Cs06','Gs06','Cs03','Ms03'],
}
2.4 The Device Driver Translate Tables
The driver translate tables define how point and signal
numbers translate into I2C bus addresses.
To drive the 42
points with two active settings (L/R
direction)
requires 84 active leads. This complexity is reduced by the
use of crossbar switching, which reduces the number of
transistors from 84 to 20. These are arranged in two banks
of 2x8 and 1x4 groups, with the first group driving one side
of the active pairs, and the second group driving the other
side. Selection within a group is done by the I2C bus address.
"pointsDriverData.py" 2.6 ={
'Cp1':[(2,0b00000001),(0,0b00000001),(2,0b00000001),(0,0b00000010)],
'Cp2':[(2,0b00000001),(0,0b00000100),(2,0b00000001),(0,0b00001000)],
'Cp3':[(2,0b00000001),(0,0b00010000),(2,0b00000001),(0,0b00100000)],
'Cp4':[(2,0b00000001),(0,0b01000000),(2,0b00000001),(0,0b10000000)],
'Cp5':[(2,0b00000010),(0,0b00000001),(2,0b00000010),(0,0b00000010)],
'Gp1':[(2,0b00000010),(0,0b00000100),(2,0b00000010),(0,0b00001000)],
'Gp2':[(2,0b00000010),(0,0b00010000),(2,0b00000010),(0,0b00100000)],
'Gp3':[(2,0b00000010),(0,0b01000000),(2,0b00000010),(0,0b10000000)],
'Gp4':[(2,0b00000100),(0,0b00000001),(2,0b00000100),(0,0b00000010)],
'Gp5':[(2,0b00000100),(0,0b00000100),(2,0b00000100),(0,0b00001000)],
'Mp1':[(2,0b00000100),(0,0b00010000),(2,0b00000100),(0,0b00100000)],
'Mp2':[(2,0b00000100),(0,0b01000000),(2,0b00000100),(0,0b10000000)],
'Mp3':[(2,0b00001000),(0,0b00000001),(2,0b00001000),(0,0b00000010)],
'Mp4':[(2,0b00001000),(0,0b00000100),(2,0b00001000),(0,0b00001000)],
'Mp5':[(2,0b00001000),(0,0b00010000),(2,0b00001000),(0,0b00100000)],
'Mp6':[(2,0b00001000),(0,0b01000000),(2,0b00001000),(0,0b10000000)],
'Pp1':[(2,0b00000001),(1,0b00000001),(2,0b00000001),(1,0b00000010)],
'Pp2':[(2,0b00000001),(1,0b00000100),(2,0b00000001),(1,0b00001000)],
'Pp3':[(2,0b00000001),(1,0b00010000),(2,0b00000001),(1,0b00100000)],
'Pp4':[(2,0b00000001),(1,0b01000000),(2,0b00000001),(1,0b10000000)],
'Pp5':[(2,0b00000010),(1,0b00000001),(2,0b00000010),(1,0b00000010)],
'Pp6':[(2,0b00000010),(1,0b00000100),(2,0b00000010),(1,0b00001000)],
'Pp7':[(2,0b00000010),(1,0b00010000),(2,0b00000010),(1,0b00100000)],
'Pp8':[(2,0b00000010),(1,0b01000000),(2,0b00000010),(1,0b10000000)],
'Pp9':[(2,0b00000100),(1,0b00000001),(2,0b00000100),(1,0b00000010)],
'Pp10':[(2,0b00000100),(1,0b00000100),(2,0b00000100),(1,0b00001000)],
'Pp11':[(2,0b00000100),(1,0b00010000),(2,0b00000100),(1,0b00100000)],
#free:...
'Sp1':[(3,0b00000001),(0,0b00000001),(3,0b00000001),(0,0b00000010)],
'Sp2':[(3,0b00000001),(0,0b00000100),(3,0b00000001),(0,0b00001000)],
'Sp3':[(3,0b00000001),(0,0b00010000),(3,0b00000001),(0,0b00100000)],
'Sp4':[(3,0b00000001),(0,0b01000000),(3,0b00000001),(0,0b10000000)],
'Sp5':[(3,0b00000010),(0,0b00000001),(3,0b00000010),(0,0b00000010)],
'Sp6':[(3,0b00000010),(0,0b00000100),(3,0b00000010),(0,0b00001000)],
'Sp7':[(3,0b00000010),(0,0b00010000),(3,0b00000010),(0,0b00100000)],
'Sp8':[(3,0b00000010),(0,0b01000000),(3,0b00000010),(0,0b10000000)],
'Sp9':[(3,0b00000100),(0,0b00000001),(3,0b00000100),(0,0b00000010)],
'Sp10':[(3,0b00000100),(0,0b00000100),(3,0b00000100),(0,0b00001000)],
'Sp11':[(3,0b00000100),(0,0b00010000),(3,0b00000100),(0,0b00100000)],
'Sp12':[(3,0b00000100),(0,0b01000000),(3,0b00000100),(0,0b10000000)],
'Sp13':[(3,0b00001000),(0,0b00000001),(3,0b00001000),(0,0b00000010)],
'Sp14':[(3,0b00001000),(0,0b00000100),(3,0b00001000),(0,0b00001000)],
'Sp15':[(3,0b00001000),(0,0b00010000),(3,0b00001000),(0,0b01000000)],
#free:...
}
"signalsDriverData.py" 2.7 ={
'Cs1':[(0,0b00000001),(0,0b00000010)],
'Cs2':[(0,0b00000100),(0,0b00001000)],
'Cs3':[(0,0b00010000),(0,0b00100000)],
'Cs4':[(0,0b01000000),(0,0b10000000)],
'Cs5':[(1,0b00000001),(1,0b00000010)],
'Cs6':[(1,0b00000100),(1,0b00001000)],
'Cs7':[(1,0b00010000),(1,0b00100000)],
'Cs8':[(1,0b01000000),(1,0b10000000)],
'Cs9':[(2,0b00000001),(2,0b00000010)],
'Gs1':[(2,0b00000100),(2,0b00001000)],
'Gs2':[(2,0b00010000),(2,0b00100000)],
'Gs3':[(2,0b01000000),(2,0b10000000)],
'Gs4':[(3,0b00000001),(3,0b00000010)],
'Gs5':[(3,0b00000100),(3,0b00001000)],
'Gs6':[(3,0b00010000),(3,0b00100000)],
'Ms1':[(3,0b01000000),(3,0b10000000)],
'Ms2':[(4,0b00000001),(4,0b00000010)],
'Ms3':[(4,0b00000100),(4,0b00001000)],
'Ms4':[(4,0b00010000),(4,0b00100000)],
'Ps1':[(4,0b01000000),(4,0b10000000)],
'Ps2':[(5,0b00000001),(5,0b00000010)],
'Ps3':[(5,0b00000100),(5,0b00001000)],
'Ps4':[(5,0b00010000),(5,0b00100000)],
'Ps5':[(5,0b01000000),(5,0b10000000)],
'Ps6':[(6,0b00000001),(6,0b00000010)],
'Ps7':[(6,0b00000100),(6,0b00001000)],
'Ps8':[(6,0b00010000),(6,0b00100000)],
'Ps9':[(6,0b01000000),(6,0b10000000)],
#free:[(7,0b00000001),(7,0b00000010)],
#free:[(7,0b00000100),(7,0b00001000)],
#free:[(7,0b00010000),(7,0b00100000)],
#free:[(7,0b01000000),(7,0b10000000)],
#
'Ss1':[(8,0b00000001),(8,0b00000010)],
'Ss2':[(8,0b00000100),(8,0b00001000)],
'Ss3':[(8,0b00010000),(8,0b00100000)],
'Ss4':[(8,0b01000000),(8,0b10000000)],
'Ss5':[(9,0b00000001),(9,0b00000010)],
'Ss6':[(9,0b00000100),(9,0b00001000)],
'Ss7':[(9,0b00010000),(9,0b00100000)],
'Ss8':[(9,0b01000000),(9,0b10000000)],
'Ss9':[(0b1010,0b00000001),(0b1010,0b00000010)],
}
3. The LayoutComponent Module
This code module supplies classes to define all the components
of a layout: signals, points, tracks, together with methods
to alter signals and points, and change the track status
accordingly, subject to various interlocking rules that
afford safe operation to trains using the layout.
"LayoutComponents.py" 3.1 =import getopt
import re
import sys
DEBUG=[]
SIGNALS='signalsData.py'
POINTS='pointsData.py'
TRACKS='trackData.py'
ROUTES='routesData.py'
BLOCKS='blocksData.py'
def localFlag(msg):
pass
flagError=localFlag
class Component():
'''The base class for all layout components.
Components are named according to their region, with an initial letter drawn from:
C - Cabernet
G - Grenache
M - Merlot
P - Pinot
S - Shiraz
'''
def __init__(self,name):
self.name=name
def __str__(self):
'''return a string representation of this component'''
return self.name
<LayoutComponents: Signal class 3.2>
<LayoutComponents: Point class 3.4>
<LayoutComponents: Track class 3.6>
<LayoutComponents: Route class 3.8>
<LayoutComponents: the Signals class 3.3>
<LayoutComponents: the Points class 3.5>
<LayoutComponents: the Tracks class 3.7>
<LayoutComponents: the Routes class 3.9>
<LayoutComponents: the Layout class 3.10>
<LayoutComponents: the main routine 3.18>
if __name__ == '__main__':
(vals,path)=getopt.getopt(sys.argv[1:],'dcpst:f:',
['debug','debcon','path','points','signals','controls=','conflicts='])
for (opt,val) in vals:
if opt=='-d' or opt=='--debug':
DEBUG.append('debug')
elif opt=='-c' or opt=='--debcon':
DEBUG.append('debcon')
elif opt=='--path':
DEBUG.append('path')
elif opt=='-p' or opt=='--points':
DEBUG.append('points')
elif opt=='-s' or opt=='--signals':
DEBUG.append('signals')
elif opt=='-f' or opt=='--conflicts':
conflictfile=val
elif opt=='-t' or opt=='--controls':
controlfile=val
if len(path)>0:
srcfile=path[0]
else:
srcfile='jmrData.txt'
# all set, now run the main loop
main()
This module is designed to be imported into other programs, and
defines a number of classes for use within those programs. The
key class is the Layout class, which can be instantiated
to represent an entire layout.
When called as a stand-alone program however, the main
routine runs a number of test cases to verify the correct
operation of the layout. After these few test cases are run,
the user is prompted to enter a variety of specific test cases,
such as:
Ps03G
Pp04L
These examples would (attempt to) set signal Ps03 to green, and
then throw point Pp04 to the left.
signals
tracks
points
These three examples display the signals/tracks/points data
tables respectively.
The following three subsections describe two classes for each
of the layout component types: signals, points and tracks. The
first class in each case provides a definition for a single
component, while the second class defines the collection of all
the components of that type that make up the layout.
3.1 Signals
3.1.1 The Signal class
<LayoutComponents: Signal class 3.2> =class Signal(Component):
'''Class to instantiate all instances of signals.
Attributes are:
name (inherited): signal name of the form Xsnn
X is region name, 's' is signal, nn is signal number (2 digits)
aspect: either 'R'ed or 'G'reen
permit: track segment name controlled by this signal
direction: signal faces trains in 'C'lockwise or 'A'nticlockwise direction
'''
def __init__(self,name,permit=[],direction='C',aspect='R'):
Component.__init__(self,name)
self.aspect=aspect # 'R'ed or 'G'reen, default to red
self.permit=permit # set of track segments
self.direction=direction # 'C'lockwise or 'A'nticlockwise, default to clockwise
def __str__(self):
'''return a string representation of this component'''
str="%s" % Component.__str__(self)
str+=":aspect=%s,permit=%s,direction=%s" % (self.aspect,self.permit,self.direction)
return str
def stringSignal(self):
str="'%s'" % Component.__str__(self)
str+=":['%s',%s,'%s']" % (self.aspect,self.permit,self.direction)
return str
def changeSignal(self,aspect):
'''set this signal to aspect 'R' or 'G'
Raw change only: no interlocking performed.
'''
self.aspect=aspect
The Signal class instantiates objects representing
the individual signals within the layout. These are identified
by a name, given by the rules in paragraph
<signalsData.py 2.2>. There are 4 attributes to a
signal instance:
- name
-
as identified in paragraph <signalsData.py 2.2>
- permit
-
A list of track segment names of those track segments
controlled by this signal (usually a singleton, but
defaults to empty).
- direction
-
(aka orientation) an indication of whether the signal is
oriented against trains travelling in the clockwise or
anticlockwise direction (defaults to clockwise).
- aspect
- the indication shown by the signal, either red,
green, or yellow (defaults to red).
The stringSignal method returns a string containing
indications of the values of these 4 attributes.
The changeSignal method changes the aspect of the
signal to the given value. No check is made if this value
is legitimate.
3.1.2 The Signals class
<LayoutComponents: the Signals class 3.3> =class Signals():
def __init__(self):
self.signalsfname=''
self.signals={}
return
def load(self,fname):
''' load the signals table from the file with name fname. The table
must be specified as a python-format dictionary definition, where
the signal name is the key to a list value containing permit,direction,aspect
'''
self.signalsfname=fname
signalsfile=open(fname)
signalstr=signalsfile.read()
signalsfile.close()
signaldefs=eval(signalstr)
keys=signaldefs.keys()
keys.sort()
for key in keys:
val=signaldefs[key]
self.signals[key]=Signal(key,permit=val[1],direction=val[2],aspect=val[0])
return
def __str__(self):
'''return a string representation of this component'''
keys=self.signals.keys()
keys.sort()
str=''
for key in keys:
str+=' %s' % (self.signals[key])
return str
def stringSignals(self):
sigs=self.signals
keys=sigs.keys()
keys.sort()
str='{\n'
str+=' # key:[aspect,controls,orientation]\n'
for s in keys:
signal=sigs[s]
str+=' %s,\n' % (signal.stringSignal())
str+='}\n'
return str
The Signals class manages all the signals associated
with a given layout. This is specified by the persistent
data file <signalsData.py 2.2>, which is a Python
dictionary definition where the entries are labelled by
signal names, and the values for each key define the 3
subordinate signal attributes, namely, permit, direction,
and aspect.
The definition is read and evaluated to define the
signals attribute of class instances. This is the
only externaly useful attribute of the class.
The stringSignals method returns a string in the same
format as used in the <signalsData.py 2.2> file
(augmented with a comment showing the names of the fields),
and is suitable for saving back to the file, or for
debugging purposes.
3.2 Points
3.2.1 The Point class
<LayoutComponents: Point class 3.4> =class Point(Component):
'''define a point object, a subclass of a layout Component.
A point has the following variables:
name - inherited from Component
setting - the way the points are set, Left or Right,
as seen from the heel or facing end.
direction - the direction from which the facing end
is encountered first.
left - the track segment when set to the left
right - the track segment when set to the right
centre - the track segment when set to the centre
(optional, only used for 3-way points)
'''
def __init__(self,name,setting='L',direction='C',
facing=[],left=[],right=[],centre=[]):
self.name=name
self.setting=setting # 'L'eft or 'R'ight, default to left
self.direction=direction # 'C'lockwise or 'A'nticlockwise,
# default to clockwise
self.left=left # set of track segments on left setting
self.centre=centre
self.right=right # set of track segments on right setting
self.facing=facing # set of track segments on facing setting
def __str__(self):
'''return a string representation of this component'''
str="'%s'" % Component.__str__(self)
str+=":['%s','%s',%s,%s,%s]" % \
(self.setting,self.direction,self.facing,self.left,self.right)
return str
def changePoint(self,setting):
'''set point to either 'L' or 'R'
L - points set left (from facing position)
R - points set right (from facing position)
C - points set centre (from facing position)
Raw change only: no interlocking performed.
'''
self.setting=setting
def changeRight(self):
'''deprecated in favour of changePoint('R')'''
self.setting='R'
def changeLeft(self):
'''deprecated in favour of changePoint('L')'''
self.setting='L'
3.2.2 The Points class
<LayoutComponents: the Points class 3.5> =class Points():
def __init__(self):
self.points={}
return
def load(self,fname):
''' load the points table from the file with name fname. The table
must be specified as a python-format dictionary definition, where
the point name is the key to a list value containing
setting,direction,facing,left,right,centre(if present)
'''
pointsfname=fname
pointsfile=open(fname)
pointstr=pointsfile.read()
pointsfile.close()
pointdefs=eval(pointstr)
keys=pointdefs.keys()
keys.sort()
for key in keys:
val=pointdefs[key]
# check here for 3-way points
if len(val)==5:
self.points[key]=Point(key,setting=val[0],direction=val[1],\
facing=val[2],left=val[3],right=val[4])
elif len(val)==6:
# points are 3-way
self.points[key]=Point(key,setting=val[0],direction=val[1],\
facing=val[2],left=val[3],right=val[4],\
centre=val[5])
else:
flagError("Error in points definition for points %s" % (key))
return
def __str__(self):
'''return a string representation of this component'''
keys=points.keys()
keys.sort()
str=''
first=True
for key in keys:
if first:
str+=' %s' % (points[key])
first=False
else:
str+=', %s' % (points[key])
return str
def stringPoints(self):
keys=self.points.keys()
keys.sort()
str='{\n'
str+=' # key:[throw,orientation,[facing segments],[left segments],[right segments]'
first=True
for key in keys:
str+='\n %s,' % (self.points[key])
str+='\n}\n'
return str
3.3 Tracks
3.3.1 The Track class
<LayoutComponents: Track class 3.6> =class Track(Component):
def __init__(self,name,status='X',clock=[],anti=[]):
self.name=name
self.status=status # 'X'cluded, 'C'lear, 'S'et, 'O'ccupied, '-'non-existent, default to excluded
self.clock=clock # set of track segments in clockwise direction
self.anti=anti # set of track segments in anticlockwise direction
self.signal=None # this segment controlled by this signal
def __str__(self):
str="'%s'" % Component.__str__(self)
str+=":['%s',%s,%s]" % (self.status,self.clock,self.anti)
return str
def setStatus(self,status):
'''set track segment to have new status 'X','S','C','O'
- - track is non-existent
X - track is out of use, or not set for travel
S - track is set, ready for use
C - track is cleared for traffic
O - track is occupied (not currently used)
Raw change only: no interlocking performed.
'''
self.status=status
3.3.2 The Tracks class
<LayoutComponents: the Tracks class 3.7> =class Tracks():
def __init__(self):
self.tracks={}
return
def load(self,fname):
''' load the tracks table from the file with name fname. The table
must be specified as a python-format dictionary definition, where
the track name is the key to a list value containing status,clock,anti
'''
tracksfname=fname
tracksfile=open(fname)
trackstr=tracksfile.read()
tracksfile.close()
trackdefs=eval(trackstr)
keys=trackdefs.keys()
keys.sort()
for key in keys:
val=trackdefs[key]
self.tracks[key]=Track(key,status=val[0],clock=val[1],anti=val[2])
return
def __str__(self):
keys=tracks.keys()
keys.sort()
str=''
first=True
for key in keys:
if first:
str+=' %s' % (tracks[key])
first=False
else:
str+=', %s' % (tracks[key])
return str
def stringTracks(self):
trks=self.tracks
keys=trks.keys()
keys.sort()
str='{\n'
str+=' # key:[status,[clockwise],[anticlockwise]]\n'
for t in keys:
str+=' %s,\n' % (trks[t])
str+='}\n'
return str
def setTrack(self,track):
'''a track that is set means it is ready to be cleared once the
controlling signal is also cleared.
'''
if not track: return
if not self.tracks.has_key(track):
flagError("No definition for track %s" % (track))
return
track=self.tracks[track]
track.setStatus('S')
def excludeTrack(self,track):
if 'tracks' in DEBUG:
print "excluding track %s" % (track)
if not track: return
if not self.tracks.has_key(track):
flagError("No definition for track %s" % (track))
return
track=self.tracks[track]
track.setStatus('X')
3.4 Routes
3.4.1 the Route class
<LayoutComponents: Route class 3.8> =class Route(Component):
def __init__(self,name,value):
self.name=name
self.route=value
pass
def _str__(self):
str="'%s'" % Component.__str__(self)
str="%s" % (self.route)
return str
3.4.2 the Routes class
<LayoutComponents: the Routes class 3.9> =class Routes():
def __init__(self):
self.routes={}
pass
def load(self,fname):
routesfname=fname
routesfile=open(fname)
routesstr=routesfile.read()
routesfile.close()
routesdefs=eval(routesstr)
keys=routesdefs.keys()
keys.sort()
for key in keys:
val=routesdefs[key]
self.routes[key]=Route(key,val)
return
def __str__(self):
keys=routes.keys()
keys.sort()
str=''
first=True
for key in keys:
if first:
str+=' %s' % (routes[key])
first=False
else:
str+=', %s' % (routes[key])
return str
3.5 Blocks
3.6 The Layout class
<LayoutComponents: the Layout class 3.10> =class Layout():
global DEBUG
def __init__(self):
self.signalsData=Signals()
self.signals=self.signalsData.signals
self.pointsData=Points()
self.points=self.pointsData.points
self.tracksData=Tracks()
self.tracks=self.tracksData.tracks
self.routesData=Routes()
self.routes=self.routesData.routes
self.clearedBlocks=[]
self.occupiedBlocks=[]
return
def load(self):
self.signalsData.load(SIGNALS)
self.signals=self.signalsData.signals
self.pointsData.load(POINTS)
self.points=self.pointsData.points
self.tracksData.load(TRACKS)
self.tracks=self.tracksData.tracks
self.routesData.load(ROUTES)
self.routes=self.routesData.routes
f=open(BLOCKS,'r')
fst=f.readline().strip()
if fst:
cleared=occupied=[]
exec(fst)
self.clearedBlocks=cleared
self.occupiedBlocks=occupied
# basic data initialized, now compute segment-signal relationships
permissions={}
keys=self.signals.keys()
for s in keys:
permit=self.signals[s].permit[0]
permissions[permit]=s
self.permissions=permissions
def __str__(self):
str='Signals:\n '
sigs=self.signals
keys=sigs.keys()
keys.sort()
for s in keys:
str+='%s,' % (sigs[s])
str+='\nPoints:\n '
pnts=self.points
keys=pnts.keys()
keys.sort()
for p in keys:
str+='%s,' % (pnts[p])
str+='\nTracks:'
trks=self.tracks
keys=trks.keys()
keys.sort()
first=''
for t in keys:
track=trks[t]
if track.name[0:2]!=first:
str+='\n '
first=track.name[0:2]
str+='%s,' % (track)
str+='\nSigSegs:\n'
perms=self.permissions
keys=perms.keys()
keys.sort()
for t in keys:
str+=' %s:%s,\n' % (t,perms[t])
return str
def stringBlocks(self):
clrs=self.clearedBlocks
occs=self.occupiedBlocks
str='cleared=['
for s in clrs:
str+="'%s'," % (s)
str+='];'
str+='occupied=['
for s in occs:
str+="'%s'," % (s)
str+=']\n'
return str
def changePoint(self,point,newsetting,interlock=True):
if 'points' in DEBUG:
print "changePoint(%s,%s,%s)" % (point,newsetting,interlock)
# check interlocking
if not self.points.has_key(point):
flagError("No definition for point %s" % (point))
return
thispoint=self.points[point]
if interlock:
cursetting=thispoint.setting
thistrack=point+cursetting
# check protection for thispoint
protected=self.trackProtected(thistrack)
if not protected:
flagError("points %s are not protected against conflicting movements" % (point))
return
# now set points, and clear route
thispoint.changePoint(newsetting)
if 'points' in DEBUG:
print "changePoint(%s,%s) changed" % (point,newsetting)
left="%sL" % (point)
right="%sR" % (point)
centre="%sC" % (point)
# check if centre exists
if not self.tracks.has_key(centre):
centre=''
setlist=[]
if newsetting=='L':
if 'points' in DEBUG:
print "changePoint(%s,%s): left=%s" % (point,newsetting,left)
self.tracksData.setTrack(left)
setlist=thispoint.left
if 'points' in DEBUG:
print "changePoint(%s,%s): right=%s" % (point,newsetting,right)
self.tracksData.excludeTrack(right)
self.tracksData.excludeTrack(centre)
elif newsetting=='R':
if 'points' in DEBUG:
print "changePoint(%s,%s): left=%s" % (point,newsetting,left)
self.tracksData.setTrack(right)
setlist=thispoint.right
if 'points' in DEBUG:
print "changePoint(%s,%s): right=%s" % (point,newsetting,right)
self.tracksData.excludeTrack(left)
self.tracksData.excludeTrack(centre)
elif newsetting=='C':
if 'points' in DEBUG:
print "changePoint(%s,%s): centre=%s" % (point,newsetting,centre)
self.tracksData.setTrack(centre)
setlist=thispoint.centre
if 'points' in DEBUG:
print "changePoint(%s,%s): left=%s" % (point,newsetting,left)
print "changePoint(%s,%s): right=%s" % (point,newsetting,right)
self.tracksData.excludeTrack(left)
self.tracksData.excludeTrack(right)
else:
flagError("bad point setting for %s:%s" % (point,newsetting))
return
# now set tracks controlled by this point
for t in setlist:
if 'points' in DEBUG:
print "changePoint: clears track %s" % (t)
self.tracksData.setTrack(t)
return
<LayoutComponents: Layout class: setClearPath method 3.14>
<LayoutComponents: Layout class: checkInterlocking method 3.11>
<LayoutComponents: Layout class: changeSignal method 3.13>
<LayoutComponents: Layout class: getPath method 3.15>
<LayoutComponents: Layout class: collectSignals method 3.16>
<LayoutComponents: Layout class: trackProtected method 3.17>
def close(self):
str=self.signalsData.stringSignals()
f=open(SIGNALS,'w')
f.write(str)
f.close()
str=self.pointsData.stringPoints()
f=open(POINTS,'w')
f.write(str)
f.close()
str=self.tracksData.stringTracks()
f=open(TRACKS,'w')
f.write(str)
f.close()
str=self.stringBlocks()
f=open(BLOCKS,'w')
f.write(str)
f.close()
return
def stringRepr(self):
str=self.signalsData.stringSignals()
str+=self.pointsData.stringPoints()
str+=self.tracksData.stringTracks()
return str
3.6.1 the checkInterlocking method
<LayoutComponents: Layout class: checkInterlocking method 3.11> =def checkInterlocking(self,signal):
# first get the path from this signal in direction dirn.
thissignal=self.signals[signal]
# first check if conflicting tracks are not cleared
controlled=thissignal.permit
if len(controlled)>1:
# yes, check conflicting tracks are not cleared
for t in controlled[1:]:
trackseg=self.tracks[t]
if trackseg.status in ['C','O']:
flagError("conflicting track at %s" % (t))
return (False,None)
# others OK, now check path
controltrack=controlled[0]
dirn=thissignal.direction
if 'interlock' in DEBUG:
print "<P>checkInterlocking(signal=%s,thissignal=%s,controltrack=%s,dirn=%s)" % \
(signal,thissignal,controltrack,dirn)
(path,failtrack,reason)=self.
getPath(controltrack,dirn,[])
if 'interlock' in DEBUG:
print "<P>checkInterlocking has path %s" % (path)
# scan this path checking for signal constraints
firstFaceRed=False
<checkInterlocking: check all segments in path 3.12>
# run out of pathway, why? Need to look at last element of path
# does it exist? Is its status 'X'?
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: at end of path %s" % (p)
print "<BR>checkInterlocking: with permissions=%s" % (self.permissions)
lasttrack=self.tracks[p]
if dirn=='C':
nexttrack=lasttrack.clock
else:
nexttrack=lasttrack.anti
# check if there is a nexttrack. We might be at a buffer stop
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: at buffer? lasttrack=%s,nexttrack=%s" % \
(lasttrack,nexttrack)
if not nexttrack:
# buffer stop, all OK to here
return (True,path)
# end of path, check if we are at a red signal?
if self.permissions.has_key(nexttrack[0]):
# there is a signal, is it facing?
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: found last facing at %s" % (nexttrack[0])
lastsignalname=self.permissions[nexttrack[0]]
lastsignal=self.signals[lastsignalname]
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: signal is %s" % (lastsignal)
if lastsignal.direction==d:
# yes, is it red?
if lastsignal.aspect=='R':
# yes, OK
return (True,path)
else:
# no, but this should not happen?
flagError("green signal at %s controls unset track %s" % (lastsignalname,nexttrack))
# trailing direction, ignore
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: got nexttrack %s" % (nexttrack)
if nexttrack: # route continues, but not set
flagError("route not set at %s" % (nexttrack[0]))
return (False,None)
return (True,path)
From a given signal, check if route is set in given
direction dirn. The constraints for a route to be cleared
are:
- If signal is currently green, the interlocking is
skipped. (This is done by changeSignal, before control
gets to here.)
- Track must be set (not clear) to the next facing
signal, whether it be red or green.
- If any trailing signal passed is green, FAIL.
- If next facing is green, OK and stop checking
interlocking at this point.
- If the next facing is red, must continue checking to
the facing signal after that, and ensure that it is red
also, when OK,
- If second trailing signal is green, FAIL.
- If there is no facing signal before the end of the
route (current one excepted), the route ends at buffer
stops, and the route may be cleared OK.
NO! This needs further thought. The next segment may be 'X'
for good reason. Need an explicit end of track test!!
Where a signal has more than one controlled track, it is
assumed that the first track segment is the segment directly
controlled by the signal, while others listed must be not
cleared for this signal to be cleared. This situation
arises where there is direct track conflict, such as at
track crossings.
<checkInterlocking: check all segments in path 3.12> =thisblock=[]; thisdir=''
for (p,d) in path:
if 'interlock' in DEBUG:
s=self.tracks[p].status
print "<BR>checkInterlocking: explore path at %s with status %s" % (p,s)
if not thisdir: thisdir=d
# is track p set?
if self.tracks[p].status not in ['S']:
# no, not enough set pathway or conflicting cleared pathway to check signals
# but first, check that we are not looking beyond a facing green
if self.permissions.has_key(p):
sig=self.permissions[p]
thissignal=self.signals[sig]
# is it facing or trailing?
facing=thissignal.direction==d
if facing and thissignal.aspect=='G':
# can safely ignore
thisblock.append(p)
return (True,thisblock)
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: path not clearable at %s" % (p)
flagError("route not set at segment %s" % (p))
return (False,None)
# track p is set, so check for signal
if self.permissions.has_key(p):
# there is one, so get it
sig=self.permissions[p]
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: signal %s at path %s" % (sig,p)
thissignal=self.signals[sig]
# is it facing or trailing?
facing=thissignal.direction==d
if not facing:
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: trailing signal at %s" % (thissignal)
# must be red
if thissignal.aspect!='R':
# signal constraint fail
flagError("opposing movement signal %s is green" % (thissignal.name))
return (False,None)
# is red, count first trailing passed
else:
# is a facing signal
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: facing signal at %s" % (thissignal)
if thissignal.aspect=='G':
# is green, so can stop checking interlock
return (True,path)
# facing is red, check if first
elif firstFaceRed:
# passed the first facing, so we can exit with success!
return (True,path)
# this is first facing red, must continue checking until next
firstFaceRed=True
if 'interlock' in DEBUG:
print "<BR>checkInterlocking: passing first face red at %s" % \
(thissignal)
# and press on
pass # end if not facing
pass # if self.permissions
pass # for p
For every track segment p in the path, we check that its
status is set.
3.6.2 the changeSignal method
<LayoutComponents: Layout class: changeSignal method 3.13> =def changeSignal(self,signal,aspect):
'''change a signal aspect with interlocking. Returned is success or failure, and:
- in the case of failure, the track segment or signal that fails
the interlocking constraints;
- in the case of success, the block that has been cleared
(which is added to the class variable clearedBlocks).
'''
#print "<P>layout changeSignal, DEBUG=%s" % (DEBUG)
thissignal=self.signals[signal]
if 'signals' in DEBUG:
print "changeSignal: current %s.aspect=%s" % (signal,thissignal.aspect)
if aspect=='R':
# can always change signal to red
thissignal.changeSignal('R')
return (True,None)
# but for green, must check interlocking
# however, if already green, ignore and don't check interlocking
if thissignal.aspect=='G':
return (True,None)
# currently red, must check interlocking
(clear,block)=self.checkInterlocking(signal)
if clear:
thissignal.changeSignal('G')
if thissignal.name not in self.clearedBlocks:
self.clearedBlocks.append(thissignal.name)
return (True,block)
else:
return (False,None)
3.6.3 the setClearPath method
<LayoutComponents: Layout class: setClearPath method 3.14> =def setClearPath(self,signal,lay,status):
'''set, clear or occupy path to next (but not included) facing signal'''
thissignal=self.signals[signal]
controltrack=thissignal.permit[0]
dirn=thissignal.direction
(path,failtrack,reason)=self.
getPath(controltrack,dirn,[])
if 'segments' in DEBUG: print "setClearPath: looking at path %s" % (path)
for (p,d) in path:
if self.permissions.has_key(p):
# found a signal, is it facing?
s=self.permissions[p]
sig=self.signals[s]
if sig.direction==d:
# yes, but is it the starting signal?
if s!=signal:
# not the starter, so exit done
return
# no, keep going
# clear the track
self.tracks[p].setStatus(status)
lay.setSegment(p,status)
pass # for p
return
3.6.4 the getPath method
<LayoutComponents: Layout class: getPath method 3.15> =def
getPath(self,track,direction,sofar):
'''sofar is a list of tuples of (tracksegment,direction), because
some tracks traversed may be reversing tracks.
The returned value is a tuple:
(list of tracks in the path that are set
,
next track not in path,
status of next track not in path)
'''
if 'path' in DEBUG: print "<P/>getPath(%s,%s,%s)" % (track,direction,sofar)
if (track,direction) in sofar: # closed loop, return
return (sofar,None,None)
tracks=self.tracks
retval=(sofar,None,None)
if not tracks or not tracks.has_key(track):
return (sofar,track,'X')
st=tracks[track].status
if 'path' in DEBUG: print "track %s has status %s" % (track,st)
if len(sofar)>=1:
if 'path' in DEBUG: print "looking at %s, sofar=%s" % (track,sofar)
(lasttrack,lastdirn)=sofar[-1]
else:
lasttrack=None
if st in ['S','C','O']:
sofar.append((track,direction))
if tracks[track].status not in ['S','C','O']:
return (sofar,track,tracks[track].status)
if direction=='C':
tracks=tracks[track].clock
elif direction=='A':
tracks=tracks[track].anti
else:
print "Bad direction flag:%s" % (direction)
# check what choices
if 'path' in DEBUG: print "getPath: what to choose from:%s (sofar=%s)" % (tracks,sofar)
nalts=len(tracks)
if nalts==0:
# hit the buffers, add current track and return
if 'path' in DEBUG: print "getPath: 0 alternatives, sofar=%s" % (sofar)
return (sofar,track,'-')
elif nalts==1:
# only one choice, recurse to look further
if 'path' in DEBUG: print "getPath: 1 alternative down track %s with lasttrack=%s,sofar=%s" % (tracks,lasttrack,sofar)
if tracks[0]==lasttrack:
# reversing track, change direction, and recurse for rest
if direction=='C':
return self.
getPath(track,'A',sofar[0:-1])
else:
return self.
getPath(track,'C',sofar[0:-1])
tt=tracks[0]
if 'path' in DEBUG: print "getPath: 1 alternative, new track heading %s" % (tt)
if 'path' in DEBUG: print "getPath: 1 alternative, sofar=%s" % (sofar)
return self.
getPath(tt,direction,sofar)
else:
# check first if reversing track
if lasttrack in tracks:
# reversing track, change direction, and recurse for rest
if direction=='C':
return self.
getPath(track,'A',sofar[0:-1])
else:
return self.
getPath(track,'C',sofar[0:-1])
# multiple choices, try them all
tt=tracks; status='X'
while tt:
if 'path' in DEBUG: print "getPath: while tt=%s" % (tt)
# careful about reference semantics here!
possibletrack=tt[0]
if self.tracks.has_key(possibletrack):
status=self.tracks[possibletrack].status
if self.tracks[possibletrack].status in ['S','C','O']:
return self.
getPath(possibletrack,direction,sofar)
else:
if 'path' in DEBUG: print "getPath: no track at %s" % (possibletrack)
tt=tt[1:] # take remainder
# all alternatives exhausted, no path
return (sofar,possibletrack,status)
getPath is one of the key methods in determining the
interlocking requirements. It is a recursive method,
finding the path of set or clear track segments from the
given track segment track in the direction
direction, until a track segment is found that is not
set or clear (that is, either unknown or occupied). A list
of the track segments traversed is returned. The parameter
sofar is supplied on recursive calls to indicate the
path so far identified.
getPath returns a tuple of: the tracks that make up
the path found; the next track in sequence that does not
have a set or clear status; and the actual status of the
next track.
There are a couple of subtleties.
- A buffer stop indicates that the route found so far
is what is returned. The next track is the buffer stop
track, and the status is '-'.
- Multiple tracks (such as occur at the heel of a set
of points) are checked to see which path is set or
clear, and the appropriate one chosen (it is an error
for more than one track at a branch to be set or
cleared). If no branch is set or clear, then the path
so far is returned, with the next possible track being
the last alternative checked, along with its
status.
- Some tracks are reversing tracks, meaing that the
nominal direction of travel has reversed. This must be
taken into account when checking signals, and hence the
sofar value returned is a list of tuples, of the form
(tracksegment,direction). This is particularly
important in the routines checkInterlocking,
collectSignals, and clearTrack.
3.6.5 the collectSignals method
<LayoutComponents: Layout class: collectSignals method 3.16> =def collectSignals(self,track,dir):
'''given a track segment and direction, compute what signals lie
upon the current path define by these parameters. Returns a list
of tuples in track direction order of [(sig,dir),...] where dir is
either 'F'acing or 'T'railing
'''
retval=[]
(path,segment,reason)=self.
getPath(track,dir,[])
if 'signals' in DEBUG:
print "collectSignal: collecting along path: %s,%s,%s" % (path,segment,reason)
for (seg,dir) in path:
if 'signals' in DEBUG: print "collectSignal: next path element: %s" % (seg)
if 'signals' in DEBUG: print "collectSignal: permissions=%s" % (self.permissions)
if self.permissions.has_key(seg):
# signal on this segment
sig=self.permissions[seg]
if 'signals' in DEBUG:
print "collectSignals: got a signal segment %s with signal %s" % (seg,sig)
# determine direction of signal
if self.signals[sig].direction==dir:
# facing signal, add to list
retval.append((sig,'F'))
else:
# trailing signal, add to list
retval.append((sig,'T'))
if 'signals' in DEBUG: print "collectSignals()=>%s" % (retval)
return retval
Given a track segment and direction, compute what signals
lie upon the current path define by these parameters.
Returns a list of tuples in track direction order of
[(sig,dir),...] where dir is either 'F'acing or
'T'railing
3.6.6 The trackProtected method
<LayoutComponents: Layout class: trackProtected method 3.17> =def trackProtected(self,track):
'''for a given track segment, check that all protecting signals
are red.
'''
# look in both clockwise and anticlockwise directions
if 'points' in DEBUG: print "trackProtected: checking %s" % (track)
directions=0
for d in ['C','A']:
directions+=1
sigs=self.collectSignals(track,d)
if 'points' in DEBUG: print "trackProtected: signal list is %s" % (sigs)
# if there are no signals, must be (?) dead end!
if not sigs:
if 'points' in DEBUG: print "trackProtected: protection suspect, no signals left on route (%s)" % (sigs)
return True
# rule is that the first trailing signal must be red
for (s,d) in sigs:
if d=='T': # must be red
if self.signals[s].aspect=='R': # good
if 'points' in DEBUG: print "trackProtected: protection OK in %s direction, signal %s is red" % (d,s)
break
else: # bad
if 'points' in DEBUG: print "trackProtected: protection fails,signal %s is not red" % (s)
return False
return directions==2 # only if both directions OK
Returns, for the given track segment, a True/False
indication that all signals protecting this track segment
are set to red.
3.7 The main routine
<LayoutComponents: the main routine 3.18> =def main():
# make a layout
layout=Layout()
layout.load()
keys=layout.points.keys()
for p in keys:
s=layout.points[p].setting
print "changing point %s to setting %s" % (p,s)
layout.changePoint(p,s)
print
print layout.
getPath('St06','C',[])
print layout.collectSignals('St06','C')
layout.changePoint('Sp08',"R")
print layout.
getPath('St06','C',[])
print layout.collectSignals('St06','C')
# define routines for testing
routines={'checkInterlocking':layout.checkInterlocking,
'getPath':layout.getPath,
}
# perform some testing
l='OK'
while l:
l=raw_input().strip()
res=re.match('(.)(.)(\d+),?(.)',l)
if res:
a=res.group(1).upper()
t=res.group(2)
n=res.group(3)
d=res.group(4).upper()
component=a+t+n
if t=='s':
msg1=layout.changeSignal(component,d)
msg2=layout.signals[component]
print "%s: %s" % (msg1,msg2)
elif t=='p':
msg1=layout.changePoint(component,d)
msg2=layout.points[component]
print "%s: %s" % (msg1,msg2)
elif l=='signals':
print "Signals:\n%s" % (layout.signalsData.stringSignals())
elif l=='points':
print "Points:\n%s" % (layout.pointsData.stringPoints())
elif l=='tracks':
print "Tracks:\n%s" % (layout.tracksData.stringTracks())
else:
res=re.match('([^(]+)(\((.+)\))?',l)
if res:
print res.group(1),res.group(2),res.group(3)
routine=res.group(1)
print eval("layout.%s" % (l))
print "end of %s" % (routine)
pass # if res
pass # else
print "Signals:\n%s" % (layout.signalsData.stringSignals())
print "Points:\n%s" % (layout.pointsData.stringPoints())
print "Tracks:\n%s" % (layout.tracksData.stringTracks())
layout.close()
4. The External Server Program jmr.py
"jmr.py" 4.1 =#!/usr/bin/python
## P R O G R A M N A M E
##
## **********************************************************
## * DO NOT EDIT THIS FILE! *
## * Use $HOME/Computers/Sources/jmr24/jmr.xlp instead *
## **********************************************************
##
<jmr: import modules 4.2>
<jmr: initialize globals 4.3>
<jmr: initialize fat controller interfacing 4.4>
<jmr: define subroutines 4.5>
<jmr: initialize web page 4.7>
<jmr: collect web page parameters 4.6>
<jmr: handle debug mode 4.8>
<jmr: load all data variables 4.9>
saveDEBUG=DEBUG
#DEBUG=[] # turn debugging off for initialization
svgLayout.DEBUG=DEBUG
LayoutComponents.DEBUG=DEBUG
# initial check of points table
if 'points' in DEBUG:
print "<P>",
for k in layout.points.keys():
print "%s:%s" % (k,layout.points[k]),
print "</P>"
# and force consistency with tracks:
#for k in layout.points.keys():
# point=layout.points[k]
# # and ignore interlocking constraints
# layout.changePoint(k,point.setting,False)
#print layout.pointsData.stringPoints()
# initial check of track segments
if 'tracks' in DEBUG:
print "<P>",
for k in layout.tracks.keys():
print "%s" % (layout.tracks[k]),
print "</P>"
# initial check of routes table
if 'routes' in DEBUG:
print "<H3>Routes Table</H3>"
print "<P>",
for k in layout.routes.keys():
print "%s:%s<BR>" % (k,layout.routes[k].route),
print "</P>"
# set points according to points table
# ... hmmm, think again here. Don't want to reset any cleared routes
#for k in pKeys:
# lay.setPoint(k,points[k])
# set signals according to signals table
for k in layout.signals.keys():
layout.changeSignal(k,layout.signals[k].aspect)
# set segments according to segment table
for k in layout.tracks.keys():
lay.setSegment(k,layout.tracks[k].status)
DEBUG=saveDEBUG
svgLayout.DEBUG=DEBUG
LayoutComponents.DEBUG=DEBUG
<jmr: extract the browser parameters and process them 4.10>
<jmr: close all files and windup 4.16>
<jmr: render the SVG layout diagram 4.15>
##
## The End
##
The basic operation of this server program is to scan the
call URL for parameters, and perform the actions specified
therein. Once processed, the updated layout is re-displayed.
<jmr: import modules 4.2> =import sys
sys.path.append('/home/ajh/www/cgi-bin/jmr24')
import calendar
import cgi
import os
import os.path
import re
from subprocess import *
import time
import urllib2
import urlparse
import xml.dom
from xml.dom.minidom import parse
#import interlocking
#import pointsModule
import svgLayout
import LayoutComponents
<jmr: initialize globals 4.3> =# possible values are ['init','load','point','route','signal','segment','interlock']
DEBUG=[]
regionPat=svgLayout.regionPat
numberPat=svgLayout.numberPat
localOnly=False
logfilefn="jmr.log"
logfile=open(logfilefn,'a')
errMessages=[]
server="localhost"
regionPat defines a constant pattern for matching
a region designation.
<jmr: initialize fat controller interfacing 4.4> =# Need to check whether the fat-controller is awake
# look at fatCheckFile for date of last awakening
# if this is more than a day old, recheck if fat-controller is awake,
# otherwise use the status recorded there ...
fatCheckFile='/home/ajh/www/tmp/fatCheckTime'
try:
lastFatCheck=os.path.getmtime(fatCheckFile)
except:
lastFatCheck=0
nowTime=calendar.timegm(time.localtime())
dayTime=24*60*60 # seconds in day
# is last information less than a day old?
if nowTime-lastFatCheck < dayTime:
# yes, read and use that
try:
fc=open(fatCheckFile)
fcline=fc.readline().strip()
#sys.stderr.write("fat Controller says %s" % fcline)
if fcline!='OK':
localOnly=True
fc.close()
except IOError:
sys.stderr.write("fat Controller has IOError")
localOnly=True
# these following two lines seem to be in error. Why?
#fc=open(fatCheckFile,'w')
#fc.write('OFF\n')
else:
# no, recheck if fat-controller now awake
status=os.system('ping -c1 fat-controller >/dev/null')
fc=open(fatCheckFile,'w')
if status==0:
fc.write('OK\n')
else:
fc.write('OFF\n')
localOnly=True
fc.close()
sleep='awake'
if localOnly: sleep='asleep'
sys.stderr.write("fat Controller is %s\n" % sleep)
<jmr: define subroutines 4.5> =def flagError(msg):
# save message to print later
if not msg: return
if DEBUG:
print "<BR/>flagError: %s" % (msg)
errMessages.append(msg)
LayoutComponents.flagError=flagError
def sendCommand(device,attribute):
global localOnly
if DEBUG:
print "sendCommand(%s,%s)" % (device,attribute)
fcurl="http://fat-controller/~trains/cgi-bin/trains.py?"
if localOnly:
return '(fat-controller is not awake)'
else:
try:
fc=urllib2.urlopen("%s%s=%s" % (fcurl,device,attribute),None,2)
action=''
for l in fc.readlines():
action+=l
return action
except urllib2.URLError:
localOnly=False
return 'Cannot contact the fat controller'
<jmr: collect web page parameters 4.6> =env=os.environ
if DEBUG:
keys=env.keys()
keys.sort()
for key in keys:
print "%20s: %s<BR/>\n" % (key,env[key])
server='localhost'
if env.has_key('SERVER_NAME'):
server=env['SERVER_NAME']
urlbits=urlparse.urlparse(env['REQUEST_URI'])
queries=urlparse.parse_qsl(urlbits.query,True)
<jmr: initialize web page 4.7> =print "Content-type: text/html\n"
print "<title>John's Model Railway</title>"
print "<H2>WELCOME TO JOHN'S MODEL RAILWAY!</H2>",
<jmr: handle debug mode 4.8> =#XLP jmr: handle debug mode
for (q,v) in queries:
if q=='debug':
DEBUG=['debug']
vals=v.split(',')
if 'all' in vals:
# omit 'init','load'
DEBUG=['interlock','path','points','routes','segments','signals','tracks']
else:
for vv in vals:
DEBUG.append(vv)
# interlocking.DEBUG=DEBUG
svgLayout.DEBUG=DEBUG
LayoutComponents.DEBUG=DEBUG
elif q=='local':
localOnly=True
if DEBUG:
print "<P>localOnly='%s'<BR/>" % (localOnly)
print "query='%s'<BR/>" % (urlbits.query)
print "queries='%s'<BR/>" % (queries)
print "DEBUG=%s</P>" % (DEBUG)
<jmr: load all data variables 4.9> =# get the Layout proper
layout=LayoutComponents.Layout()
# load the signals, points and track segments
layout.load()
# get the layout diagram
lay=svgLayout.SvgLayout('layout.svg',server)
# and set all track and signal elements
lay.getLayoutSignals()
#lay.getLayoutPoints()
<jmr: extract the browser parameters and process them 4.10> =#XLP jmr: extract the browser parameters and process them
conflict=False; reason=''; actions=''
for (query,value) in queries:
if DEBUG: print "<P>Performing (%s,%s)</P>" % (query,value)
# check for signal/point/track action
res=re.match('(%s)(s|p|t)(%s)' % (
regionPat,
numberPat),query)
if res:
<jmr: change signal or point 4.11>
elif query=='redraw':
# ultimately this should revisit all points and signals and re-set them
pass
elif query=='help':
<jmr: perform help action 4.12>
elif query=='reset':
<jmr: perform reset action 4.13>
elif query=='points':
print "<P>Points table: ",
print interlock.stringPoints()
print "</P>"
elif query=='signals':
print "<P>Signals table: ",
print interlock.stringSignals()
print "</P>"
elif query=='route':
<jmr: perform route action 4.14>
elif query in ['debug','load','track','signal','segment']:
pass # do nothing at this stage
else:
print "<P>request '%s' unknown</P>" % (query)
This section of code is responsible for performing all the work
required by this call on the jmr cgi routine. For each
query,value pair in the browser parameters, we decode the query
and call the appropriate routine with the supplied value. The
'debug', etc., queries are passed over, since they have
already been handled earlier in the
<jmr: handle debug mode 4.8> code.
<jmr: change signal or point 4.11> =#XLP jmr: change signal or point
if res.group(2)=='s': # change a signal
#print "<P>jmr change signal, DEBUG=%s" % (DEBUG)
# force signal key to upper case leading region
signal="%ss%s" % (res.group(1).upper(),res.group(3))
aspect=value.upper()
if 'interlock' in DEBUG:
print "Check Interlock on signal: %s,%s" % (signal,aspect)
curaspect=layout.signals[signal].aspect
if aspect=='T':
#pass
if curaspect=='R':
aspect='G'
else:
aspect='R'
elif aspect=='O':
# can only do this if signal is currently green
if curaspect!='G':
flagError('Signal %s is NOT cleared' % (signal))
else: # OK to occupy
aspect='R'
elif aspect=='S':
# can only do this if signal is currently red
if curaspect!='R':
flagError('Signal %s should be RED!' % (signal))
else: # OK to occupy
aspect='R'
#print "<P>jmr calling changeSignal, DEBUG=%s" % (DEBUG)
(clear,reason)=layout.changeSignal(signal,aspect)
# call fails if interlocking fails
if clear:
lay.setSignal(signal,aspect)
status='S'
if aspect=='G': status='C'
# cannot occupy if signal currently not G
if value.upper()=='O':
if curaspect=='G':
# Occupy a block, and add to list of occupieds.
status='O'
layout.occupiedBlocks.append(signal)
else:
status='S'
# Remember to remove this block from the cleared blocks
try:
layout.clearedBlocks.remove(signal)
except ValueError:
print "block %s not in cleared blocks??" % (signal)
elif value.upper()=='S':
# (re)set a block.
status='S'
try:
layout.occupiedBlocks.remove(signal)
except ValueError:
print "block %s not occupied??" % (signal)
layout.setClearPath(signal,lay,status)
else:
flagError(reason)
elif res.group(2)=='p': # change a point
# force point key to upper case leading region
point="%sp%s" % (res.group(1).upper(),res.group(3))
direction=value.upper()
if 'interlock' in DEBUG:
print "Check Interlock on point: %s,%s" % (point,direction)
layout.changePoint(point,direction)
segs=[point+'L',point+'R']
for seg in segs:
if layout.tracks.has_key(seg):
st=layout.tracks[seg].status
lay.setSegment(seg,st)
else:
flagError("There is no track segment defined for %s" % (seg))
elif res.group(2)=='t': # change a track (this must be to occupy the track segment)
track="%st%s" % (res.group(1).upper(),res.group(3))
lay.setSegment(track,'O')
<jmr: perform help action 4.12> =# list all the possibilities
fn='intro.html'
introf=open(fn,'r')
intro=introf.readlines()
for line in intro:
print line.strip()
<jmr: perform reset action 4.13> =# reset all signals to red
for k in layout.signals.keys():
lay.setSignal(k,'R')
layout.changeSignal(k,'R')
# reset all cleared track sections
for k in layout.tracks.keys():
st=layout.tracks[k].status
if 'tracks' in DEBUG: print "reset track status for track %s (status=%s)" % (k,st)
if layout.tracks[k].status=='C':
lay.setSegment(k,'S')
layout.tracks[k].setStatus('S')
layout.clearedBlocks=[]
4.1 jmr: perform route action
<jmr: perform route action 4.14> =# clear a route
# the value of the command is a route and optional direction
# e.g., inner,clock or outer,anti to show direction of clearing
# e.g., inner or outer if no direction specified
if DEBUG: print "path=%s<BR>" % (value)
if layout.routes.has_key(value):
path=layout.routes[value].route
if DEBUG: print "path=%s<BR>" % (path)
for (point,throw) in path:
if 'routes' in DEBUG:
print "setting points %s to %s on route %s<BR/>" % (point,throw,path)
layout.changePoint(point,throw)
segs=[point+'L',point+'R']
for seg in segs:
if layout.tracks.has_key(seg):
st=layout.tracks[seg].status
lay.setSegment(seg,st)
else:
flagError("There is no track segment defined for %s" % (seg))
pass # for (point,throw)
else:
print "Unknown route value=%s" % (value)
<jmr: render the SVG layout diagram 4.15> =# render the layout - this currently requires an svg-capable browser
print "<table>"
print "<tr>"
print "<td>"
browserSVGcapable=True
if browserSVGcapable:
print lay.string
else:
cmd=['/Applications/Inkscape.app/Contents/Resources/script']
cmd.append('/Users/ajh/www/cgi-bin/layout.svg')
cmd.append('--export-pdf=/Users/ajh/www/cgi-bin/layout.pdf')
print cmd
diagram=Popen(cmd, stdout=PIPE).communicate()[0]
print diagram
print '<IMG SRC="layout.pdf" width="100%"/>'
print "</td>"
jmrServer="http://%(server)s/~ajh/cgi-bin/jmr24/jmr.py" % (locals())
clearedBlocks=layout.clearedBlocks
cbString='(Click any of these links to occupy the cleared block)'
if not clearedBlocks:
clearedBlocks="There are no cleared blocks to occupy"
else:
clearedBlocks.sort()
lastb='xxxxxx'
for block in clearedBlocks:
#print "check block %s, last block=%s;" % (block,lastb)
link='<A HREF="%s?%s=o">%s</A>' % (jmrServer,block,block)
if lastb[0]!=block[0]:
cbString+='<BR>\n'
if block[0]=='C':
cbString+='Cabernet: %s' % (link)
elif block[0]=='G':
cbString+='Grenache: %s' % (link)
elif block[0]=='M':
cbString+='Merlot: %s' % (link)
elif block[0]=='P':
cbString+='Pinot: %s' % (link)
elif block[0]=='S':
cbString+='Shiraz: %s' % (link)
else:
cbString+='Somewhere: %s' % (link)
lastb=block
else:
cbString+=',%s' % (link)
clearedBlocks=cbString
occupiedBlocks=layout.occupiedBlocks
obString='(Click any of these links to clear the occupied block)'
if not occupiedBlocks:
occupiedBlocks="There are no occupied blocks to clear"
else:
occupiedBlocks.sort()
lastb='xxxxxx'
for block in occupiedBlocks:
#print "check block %s, last block=%s;" % (block,lastb)
link='<A HREF="%s?%s=s">%s</A>' % (jmrServer,block,block)
if lastb[0]!=block[0]:
obString+='<BR>\n'
if block[0]=='C':
obString+='Cabernet: %s' % (link)
elif block[0]=='G':
obString+='Grenache: %s' % (link)
elif block[0]=='M':
obString+='Merlot: %s' % (link)
elif block[0]=='P':
obString+='Pinot: %s' % (link)
elif block[0]=='S':
obString+='Shiraz: %s' % (link)
else:
obString+='Somewhere: %s' % (link)
lastb=block
else:
obString+=',%s' % (link)
occupiedBlocks=obString
print "<td>"
print "<A HREF=\"%(jmrServer)s?help\">Help Page</A>," % (locals())
print " "
print "<A HREF=\"/~ajh/computing/jmr.html\">Documentation</A>" % (locals())
if conflict:
print "<H2>An interlocking conflict occurred: "
print " the offending point/signal is shown in blue</H2>"
print "<P>Message was: %s -- " % (reason)
print "(Change this and try again)</P>"
else:
if not actions: actions='(None)'
print '''
<P>
Actions performed: %s</P>
<P>
If you find any interlocking fault, please report to the author.
</P>
''' % (actions)
print '''
<h2>Route Settings</h2>
<p>
<a href="%(jmrServer)s?route=outer">Outer</a> ...
<a href="%(jmrServer)s?route=inner">Inner</a> ...<BR>
<a href="%(jmrServer)s?route=inner2pinot">Inner and Pinot</a> ...<BR>
<a href="%(jmrServer)s?route=upper">Upper</a> ...
<a href="%(jmrServer)s?reset">Reset</a>
</p>
<h2>Signal Settings</h2>
<p>
<a href="%(jmrServer)s?route=outer,clock">Outer Clockwise</a> <BR/>
<a href="%(jmrServer)s?route=outer,anti">Outer Anticlockwise</a> <BR/>
<a href="%(jmrServer)s?route=inner,clock">Inner Clockwise</a> <BR/>
<a href="%(jmrServer)s?route=inner,anti">Inner Anticlockwise</a> <BR/>
<a href="%(jmrServer)s?route=upper,clock">Upper Clockwise</a> <BR/>
<a href="%(jmrServer)s?route=upper,anti">Upper Anticlockwise</a>
</p>
<h2>Track Occupancy</h2>
%(clearedBlocks)s
<h2>Track Clearing</h2>
%(occupiedBlocks)s
''' % (locals())
print "</td>"
print "</tr>"
print "</table>"
if errMessages:
print "<H4>Error Messages</H4>\n<OL>"
for msg in errMessages:
print "<LI>%s</LI>" % msg
print "</OL>"
Unfortunately, the layout is described as an SVG graph, and for
this to be displayed correctly, we require an SVG-capable
browser. Known to work are Safari (v5.1.7), Google Chrome
(v14.0.835.202), and Firefox (v7.0.1). Earlier versions may also
work, but have not been tested.
Browser svg-capability is indicated by a flag
browserSVGcapable, which strictly should be set by
inspecting the browser. Currently, I cheat, and just assume it
is true. (The reason for this is partly that the Inkscape code
does not work correctly.)
<jmr: close all files and windup 4.16> =# close the signals table
#interlock.closeSignals()
# close the points table
#interlock.closePoints()
# close the segments table
#interlock.closeSegments()
# close the logfile
logfile.close()
# final check of points table
if 'load' in DEBUG:
print "<P>Points table: ",
print layout.pointsData.stringPoints()
print "</P>"
# save the new layout
lay.saveLayout('layout.svg')
layout.close()
Make sure all data is preserved across different invocations
5. The fat-controller html server trains.py
5.1 The trains.py cgi script
"trains.py" 5.1 =#!/usr/bin/python
import os
import re
import sys
import urlparse
import
jmrData
import
pointsModule
DEBUG=False
regionPat=
jmrData.
regionPat
numberPat=
jmrData.
numberPat
print "Content-type: text/html\n"
env=os.environ
if DEBUG:
keys=env.keys()
keys.sort()
for key in keys:
print "%20s: %s<BR/>\n" % (key,env[key])
server='localhost'
if env.has_key('SERVER_NAME'):
server=env['SERVER_NAME']
urlbits=urlparse.urlparse(env['REQUEST_URI'])
queries=urlparse.parse_qsl(urlbits.query,True)
bstr_pos = lambda n: n>0 and bstr_pos(n>>1)+str(n&1) or ''
if 'debug' in [q for (q,v) in queries]:
DEBUG=True
pointsModule.DEBUG=True
print "<P>query='%s'</P>" % (urlbits.query)
print "<P>queries='%s'</P>" % (queries)
# get the points table
pointsInstance=pointsModule.Points()
(points,pKeys)=pointsInstance.loadPoints('pointsData.py')
pointsInstance.loadDriverData('pointsDriverData.py')
# get the signals
#(signals,sKeys)=signalsModule.loadSignals('signals.py')
# signals driver yet to be coded
# do a translation dump
if DEBUG:
keys=pointsInstance.pTrans.keys()
keys.sort()
print "<H2>Points Driver Translation Table</H2>"
for k in keys:
print " %s : %s<BR/>" % (k,pointsInstance.pTrans[k])
# extract the parameters and process
conflict=False; reason=''
if not queries:
queries=[('help','')]
for (query,value) in queries:
if DEBUG: print "<P>Performing (%s,%s)</P>" % (query,value)
# check for signal/point action
res=re.match('(%s)(s|p)(%s)' % (
regionPat,
numberPat),query)
if res:
#if res.group(2)=='s': # change a signal
# # force signal key to upper case leading region
# signal="%ss%s" % (res.group(1).upper(),res.group(3))
# aspect=value.upper()
# if aspect=='T':
# curaspect=pointsInstance.signals[signal]
# if curaspect=='R':
# aspect='G'
# else:
# aspect='R'
# (c,r,f)=pointsInstance.setSignal(signal,aspect)
if res.group(2)=='p': # change a point
# force point key to upper case leading region
point="%sp%s" % (res.group(1).upper(),res.group(3))
direction=value.upper()
print pointsInstance.setPoint(point,direction)
pass
pass
elif query=='redraw':
# ultimately this should revisit all points and signals and re-set them
pass
elif query=='help':
# list all the possibilities
fn='intro.html'
introf=open(fn,'r')
intro=introf.readlines()
for line in intro:
print line.strip()
elif query=='reset':
# reset all signals to red
#for k in sKeys:
# lay.setSignal(k,'R')
# pointsInstance.setSignal(k,'R')
pass # not implemented
elif query=='points':
print "<P>Points table: ",
print pointsInstance.stringPoints()
print "</P>"
elif query=='signals':
print "<P>Signals table: ",
#print pointsInstance.stringSignals()
print "</P>"
elif query=='debug':
pass # do nothing at this stage
else:
print "<P>action '%s' unknown</P>" % (query)
# set the signals table
#pointsInstance.closeSignals()
# set the points table
pointsInstance.closePoints()
# final check of points table
if DEBUG:
#print "<P>Signals table: ",
#print pointsInstance.stringSignals()
#print "</P>"
print "<P>Points table: ",
print pointsInstance.stringPoints()
print "</P>"
##
## The End
##
6. The Points Module
The points module deals with all the logic for driving the
points motor. It maintains tables indicating the current state
(in particular, the Python data structure points stored
in the file pointsData.py). When used in the context of
the fat-controller server, it also issues the appropriate USB
data to driver the I2C bus for the points.
"pointsModule.py" 6.1 =# P O I N T S M o d u l e
#
import re
import
jmrData
regionPat=
jmrData.
regionPat
numberPat=
jmrData.
numberPat
directionPat=
jmrData.
directionPat
DEBUG=False
bstr_pos = lambda n: n>0 and bstr_pos(n>>1)+str(n&1) or ''
def binary8(d):
r=''
for i in range(8):
digit=(d % 2)
r="%1d%s" % (digit,r)
d=d/2
return r
class Points():
def __init__(self):
''' create the key attributes:
* the points table
* filenames for all the above '''
self.points={}
self.pointsfn=''
"pointsModule.py" 6.2 = def
loadPoints(self,fn):
''' load the points table from the file with name fn'''
self.pointsfn=fn
pointsfile=open(fn)
pointstr=pointsfile.read()
pointsfile.close()
points=eval(pointstr)
keys=points.keys()
keys.sort()
self.points=points
self.pKeys=keys
return (points,keys)
Load the points table from the file with name fn. This
is part of the persistent data structure to maintain a
consistent setting of the points from one invocation to
another. See also closePoints.
"pointsModule.py" 6.3 = def loadDriverData(self,fn):
''' load the points translation table from the file with name fn'''
self.pTransfn=fn
transfile=open(fn)
transstr=transfile.read()
transfile.close()
trans=eval(transstr)
self.pTrans=trans
return
"pointsModule.py" 6.4 = def loadTrackData(self,trackfn):
trackf=open(trackfn,'r')
lines=trackf.readlines()
str=''
for l in lines:
str+=l
self.trackPaths=eval(str)
self.trackKeys=self.trackPaths.keys()
return (self.trackPaths,self.trackKeys)
"pointsModule.py" 6.5 = def stringPoints(self):
''' generate and return a python format definition of the current
points table '''
str='{\n '
last=self.pKeys[0][0]
for p in self.pKeys:
if p[0]!=last:
str+="\n "
last=p[0]
str+="'%s':'%s', " % (p,self.points[p])
str+='\n}'
return str
"pointsModule.py" 6.6 = def setPoint(self,point,direction):
bstr_pos = lambda n: n>0 and bstr_pos(n>>1)+str(n&1) or ''
reason=''
if DEBUG:
print "pointsModule setting points %s %s" % (point,direction)
if not direction in ['r','l','R','L']:
reason="invalid direction '%s'" % direction
return reason
if self.pTrans.has_key(point):
self.points[point]=direction
t=self.pTrans[point]
print "writing ..."
if direction=='L':
(ua,la)=t[0]; (ub,lb)=t[1]
elif direction=='R':
(ua,la)=t[2]; (ub,lb)=t[3]
# write to usb port
usa=binary8(ua)
lsa=binary8(la)
usb=binary8(ub)
lsb=binary8(lb)
reason= "(%d,%d),(%d,%d), or, " % (ua,la,ub,lb)
reason+= "(%8s %8s)," % (usa,lsa)
reason+= "(%8s %8s)" % (usb,lsb)
else:
reason="point %s is not defined" % (point)
print reason
return reason
"pointsModule.py" 6.7 = def
closePoints(self):
''' write the current points table to the previously specified
file name. '''
fn=self.pointsfn
f=open(fn,'w')
f.write(self.stringPoints())
f.close()
Save the points table to the file with name fn. This
is part of the persistent data structure to maintain a
consistent setting of the points from one invocation to
another. See also loadPoints.
7. The Interlocking Module
The interlocking module is responsible for maintaining the
signals and points tables, and for resolving the conflicts in
changing any of the entries therein (i.e., any signal or
point).
The points and signals are maintained in a straightforward
way. Two persistent files of point/signal settings respectively
are maintained as a python dictionary data structure
definition. These definitions are read and evaluated as python
strings, so they need to maintained as syntactically correct
definitions. Each point/signal is known by a name, where the
first (capital) letter defines the region of the layout, the
second (lower case) letter is an 's' or a 'p' to denote signal
or point respectively, and the third and subsequent digits
define the signal or point number in that region. The
corresponding data value stored for this key is the signal
setting ('R' for Red, 'G' for Green) or point setting ('L' for
Left, 'R' for Right, and occasionally 'C' for Centre (three way
points only)).
It should be obvious that points and signals cannot be changed
arbitrarily. The whole point of a green signal is to show that
the route ahead is clear of conflicting movements, both of
points, signals, and the trains controlled by them. The
possible conflicts are many, and the scheme adopted provides
generality, while allowing a degree of simplicity.
An interlocking conflict is defined when a change of a point or
signal would adverse affect the current workings. Assuming that
trains are always required to stop at a red signal, any point
movement directly ahead of the signal can be made if the signal
is red. It is therefore always possible to set signals to red,
regardless of point or other signal settings.
Conflicts are therefore divided into two groups: those for
points, controlled by the current signal settings, and those for
signals, controlled by the current point settings.
For a signal, it can only be cleared if there is a route
through to the next signal. However, signals may always be
reset to a red aspect. For a point, it may only be toggled if
the signals controlling any potential route it is on are all
red. We define two sets of conflict/control pathways: those for
a given signal (is there a route to the next signal?), and those
for a given point (is it on any route for which the
corresponding signal is green?).
These two sets of conditions are derived from a user-friendly
input language, which is in two parts:
- A list of points, where each point defines two (or
three) alternative pathways, given as a list of track segments.
- A list of signals, where the section(s) controlled by
the signal are given by a list of track segments, concluding
with a point. The point gives (through the table above) the
alternative sections controlled by this signal, up to the
next facing signal. (A "block" in railway parlance.)
A separate program (see makeJmrData.py) takes these two
lists and generates from them the two conflict/control
tables:
- sectionDefinitions.py
- conflictDefinitions.py
A section definition gives a pathway from one facing signal to
the next facing signal. For each signal there are usually
several sections, and the definitions are in the form of a
dictionary, where the signal names are keys, and the data
values are lists of sections. Each section is itself a list
of track segments to the next signal.
A conflict definition gives for each point or signal,
the conditions under which it cannot be changed. It defines for
each signal or point what settings of other signals or points
would lead to unsafe operation. Since there may be a variety of
these, a conflict is defined to exist whenever any of a variety
of situations is true. Each situation requires a number of
conditions to hold, and hence conflicts are defined by a sums of
products expression - each conflict is the AND of a number of
conditions, and the overal conflict is the OR of a number of
distinct conflicts.
"interlocking.py" 7.1 =# I N T E R L O C K I N G M o d u l e
#
import getopt
import re
import pointsModule
import sys
import
jmrData
regionPat=
jmrData.
regionPat
numberPat=
jmrData.
numberPat
directionPat=
jmrData.
directionPat
DEBUG=[]
class interlock(pointsModule.Points):
def __init__(self):
''' create the key attributes:
* the points table
* the signals table
* the points conflicts table
* the signal controls table
* filenames for all the above '''
pointsModule.Points.__init__(self)
self.signals={}
self.conflicts={}
self.paths={}
self.signalsfn=self.conflictsfn=''
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.2 = def loadSignals(self,fn):
''' load the signals table from the file with name fn. The table
must be specified as a python-format dictionary definition, where
the signal name is the key to a value drawn from the set
{'x','r','R','g','G'} '''
self.signalsfn=fn
signalsfile=open(fn)
signalstr=signalsfile.read()
signalsfile.close()
signals=eval(signalstr)
keys=signals.keys()
keys.sort()
self.signals=signals
self.sKeys=keys
return (signals,keys)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.3 = def loadSignalInterlocking(self,fn):
''' load the controls table from the file with name fn '''
self.controlsfn=fn
interlockingfile=open(fn)
interlockingstr=interlockingfile.read()
interlockingfile.close()
interlocking=eval(interlockingstr)
keys=interlocking.keys()
self.controls=interlocking
self.siKeys=keys
return (interlocking,keys)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.4 = def loadPointInterlocking(self,fn):
''' load the conflicts table from the file with name fn '''
self.conflictsfn=fn
interlockingfile=open(fn)
interlockingstr=interlockingfile.read()
interlockingfile.close()
interlocking=eval(interlockingstr)
keys=interlocking.keys()
self.conflicts=interlocking
self.piKeys=keys
return (interlocking,keys)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.5 = def loadSegments(self,fn):
''' load the segments table from the file with name fn '''
self.segmentsfn=fn
interlockingfile=open(fn)
interlockingstr=interlockingfile.read()
interlockingfile.close()
interlocking=eval(interlockingstr)
keys=interlocking.keys()
self.segments=interlocking
self.segKeys=keys
if 'load' in DEBUG:
print "loaded track segments = %s" % (interlocking)
return (interlocking,keys)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.6 = def loadRoutes(self,fn):
''' load the routes table from the file with name fn '''
self.routesfn=fn
routefile=open(fn)
routestr=routefile.read()
routefile.close()
routes=eval(routestr)
keys=routes.keys()
self.routes=routes
self.rKeys=keys
return (routes,keys)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.7 = def stringSignals(self):
''' generate and return a python format definition of the current
signals table '''
str='{\n '
last=self.sKeys[0][0]
for s in self.sKeys:
if s[0]!=last:
str+="\n "
last=s[0]
str+="'%s':'%s', " % (s,self.signals[s])
str+='\n}'
return str
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.8 = def stringSegments(self):
''' generate and return a python format definition of the current
segments table '''
str='{\n '
keys=self.segKeys
keys.sort()
last=keys[-1]
area=keys[0][0] # get area of first key
for s in keys:
if s[0]!=area:
str+="\n "
area=s[0]
str+="'%s':'%s', " % (s,self.segments[s])
str+='\n}'
return str
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.9 = def setSignal(self,signal,aspect):
clearSeg=False; fail=''
reason=''
if self.signals.has_key(signal):
(clearSeg,reason,fail)=self.signalInterlock(signal,aspect)
if clearSeg:
self.signals[signal]=aspect
else:
reason="signal %s is not defined" % (signal)
fail=signal
return (clearSeg,reason,fail)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.10 = def setSegment(self,segname,state):
if 'segment' in DEBUG:
print "setSegment(%s,%s)" % (segname,state)
self.segments[segname]=state
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.11 = def setPoint(self,point,direction):
# override pointsModule.setPoint(self,point,direction)
clearPoint=False
reason=''
if self.points.has_key(point):
(clearPoint,reason,fail)=self.pointInterlock(point,direction)
if clearPoint:
if 'interlock' in DEBUG:
print "interlock setting points %s %s" % (point,direction)
self.points[point]=direction
self.setSegment(point+direction,'S')
else:
reason="point %s is not defined" % (point)
return (clearPoint,reason,fail)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.12 = def closeSignals(self):
''' write the current signals table to the previously specified
file name. '''
fn=self.signalsfn
f=open(fn,'w')
f.write(self.stringSignals())
f.close()
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.13 = def closeSegments(self):
''' write the current segments table to the previously specified
file name. '''
fn=self.segmentsfn
f=open(fn,'w')
f.write(self.stringSegments())
f.close()
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.14 = def decodePointSignal(self,id):
if not id:
return (None,None,None)
res=re.match('(%s)(s|p)(%s)' % (
regionPat,
numberPat),id)
if res:
region=res.group(1)
component=res.group(2)
number=res.group(3)
return (region,component,number)
else:
return (None,None,None)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.15 = def signalInterlock(self,signal,aspect):
''' Check the interlocking for the signal specified,
setting it to value aspect. There must be a track segment cleared
(all components true) to the next facing signal. If there is such
a segment, return the tuple (True,segment path,None) - otherwise, if
no such segment exists, return (False,reason,fail) where reason
specifies the track segments not set correctly, and fail provides
auxiliary information about the failure.
The class entity controls must be defined.
The opposing signal at the end of the cleared track must also be
checked that it is reset.
returns True if the path is cleared, False otherwise.
'''
reason=''; fail=None
if not self.controls.has_key(signal):
reason="no controls table for %s" % (signal)
return (False,reason,signal)
controls=self.controls[signal]
if 'interlock' in DEBUG: print "controls[%s]=%s" % (signal,controls)
clear=False # clear is False until we find a path
bad=''; actual=''
for prod in controls:
for c in prod:
if 'interlock' in DEBUG: print "checking condition %s" % c
res=re.match('(.)(.)(\d+)(.)?',c)
if res:
a=res.group(1)
t=res.group(2)
n=res.group(3)
d=res.group(4)
if not d: d=''
if t=='s':
sig=a+t+n
bad=sig
if 'interlock' in DEBUG:
print "(checking signal %s for aspect %s = " % (sig,d),
actual=self.signals[sig]
elif t=='p':
pnt=a+t+n
bad=pnt
if 'interlock' in DEBUG:
print "(checking point %s for direction %s = " % (pnt,d),
actual=self.points[pnt]
elif t=='t':
pnt=a+t+n
bad=pnt
if aspect=='R':
d='C'
else:
d='S'
if 'interlock' in DEBUG:
print "(checking track %s for state %s = " % (pnt,d),
actual=self.segments[pnt]
# allow an already clear track to be re-cleared
if actual=='C' and d=='S': d='C'
cond=actual==d
if 'interlock' in DEBUG: print "%s gives %s)" % (actual,cond)
if not cond:
if 'interlock' in DEBUG: print "path %s fails at %s<BR>\n" % (prod,c)
break
else:
if 'interlock' in DEBUG: print "path %s is clear!<BR>\n" % (prod)
pass # if t
pass # if res
# if we get here, and cond is true, this path is OK
if cond:
clear=True
reason=prod
if 'interlock' in DEBUG:
print "path %s is OK! (and clear=%s)<BR>\n" % (prod,clear)
break
else:
clear=False
reason='path not clear at %s with component %s=%s' % (prod,bad,actual)
fail=bad+actual
# is signal and path being reset?
if signal[1]=='s' and aspect=='R':
# never a problem to reset signals red ...
# however, we do need to return the pathway that is being reset, if it exists.
clear=True
if 'interlock' in DEBUG:
print "signalInterlock(%s,%s)=>(%s,%s)" % (signal,aspect,clear,reason)
return (clear,reason,fail)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.16 = def pointInterlock(self,point,dirn):
''' Check the interlocking for the points specified as point,
setting it to direction dirn. If there is no conflict, return
the tuple (True,'no conflict found') - otherwise, if a conflict
exists, and it is not clear to toggle the points, return
(False,reason) where reason specifies where the conflict exists.
'''
reason='no conflict found'; fail=None
if not self.conflicts.has_key(point):
reason="no conflicts table for %s" % (point)
return (False,reason,fail)
conflicts=self.conflicts[point]
if 'interlock' in DEBUG: print "conflicts[%s]=%s" % (point,conflicts)
clear=True # clear is set False if there is a conflict
for prod in conflicts:
for c in prod:
if 'interlock' in DEBUG: print "checking condition %s" % c
res=re.match('(.)(.)(\d+)(.)?',c)
if res:
a=res.group(1)
t=res.group(2)
n=res.group(3)
d=res.group(4)
if not d: d=''
if t=='s':
sig=a+t+n
if 'interlock' in DEBUG: print "checking signal %s for aspect %s = " % (sig,d),
actual=self.signals[sig]
cond=actual==d
if 'interlock' in DEBUG: print "%s, %s" % (actual,cond)
if not cond:
if 'interlock' in DEBUG: print "path %s fails at %s<BR>\n" % (prod,c)
break
else:
if 'interlock' in DEBUG: print "path %s is clear!<BR>" % (prod)
elif t=='p':
pnt=a+t+n
if 'interlock' in DEBUG: print "checking point %s for direction %s = " % (pnt,d),
actual=self.points[pnt]
cond=actual==d
if pnt==point and d!=dirn:
if 'interlock' in DEBUG: print "%s, %s" % (actual,cond)
reason="toggling point %s in cleared path %s not allowed" % (point,prod)
fail=point+actual
return (False,reason,fail)
if 'interlock' in DEBUG: print "%s, %s" % (actual,cond)
if not cond: break
pass # test for signal or point
pass # test for match of signal or point
pass # for all components of path
# at this point, cond indicates the state of the last
# conditional component. If it is false, the path being tested
# is not relevant, and we can skip to the next path. clear
# should remain true. If it is true, the path is clear, and
# we should test the next path.
pass # for all paths
if 'interlock' in DEBUG:
print "pointInterlock(%s,%s)=>(%s,%s)" % (point,dirn,clear,reason)
return (clear,reason,fail)
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
"interlocking.py" 7.17 =def main():
# make an interlocking object
int=interlock()
int.loadSignals('signalsData.py')
int.loadPoints('pointsData.py')
int.loadSignalInterlocking('signalControls.py')
int.loadPointInterlocking('pointConflicts.py')
int.loadSegments('segmentData.py')
# perform some testing
l='OK'
while l:
l=raw_input()
res=re.match('(.)(.)(\d+),?(.)',l)
if res:
a=res.group(1).upper()
t=res.group(2)
n=res.group(3)
d=res.group(4).upper()
component=a+t+n
if t=='s':
print int.setSignal(component,d)
elif t=='p':
print int.setPoint(component,d)
print "Signals:\n%s" % (int.stringSignals())
print "Points:\n%s" % (int.stringPoints())
print int.stringSegments()
if __name__ == '__main__':
(vals,path)=getopt.getopt(sys.argv[1:],'dcpst:f:',
['debug','debcon','points','signals','controls=','conflicts='])
for (opt,val) in vals:
if opt=='-d' or opt=='--debug':
DEBUG.append('debug')
elif opt=='-c' or opt=='--debcon':
DEBUG.append('debcon')
elif opt=='-p' or opt=='--points':
DEBUG.append('points')
elif opt=='-s' or opt=='--signals':
DEBUG.append('signals')
elif opt=='-f' or opt=='--conflicts':
conflictfile=val
elif opt=='-t' or opt=='--controls':
controlfile=val
if len(path)>0:
srcfile=path[0]
else:
srcfile='jmrData.txt'
main()
Chunk defined in 7.1,
7.2,
7.3,
7.4,
7.5,
7.6,
7.7,
7.8,
7.9,
7.10,
7.11,
7.12,
7.13,
7.14,
7.15,
7.16,
7.17
8. The SVG Diagram Module
"svgLayout.py" 8.1 =
The svgLayout module is responsible for rendering a pictorial
representation of the layout. It requires an SVG-aware browser
(such as Firefox or Safari) for the display to be correctly
rendered.
At the moment, there is significant route and signal
information handled by this module. This will (should?)
eventually be moved to the jmr module or similar, to ensure
separation of concerns.
8.1 Imports and Globals
<svgLayout: import and globals 8.2> =
8.1.1 Track and Signal Colour Definitions
<svgLayout: colour definitions 8.3> =#XLP svgLayout: colour definitions
trackColourDefault='000000'
trackColourSet='449900'
trackColourClear='00ff00'
trackColourOccupied='ff7f00'
trackColourConflict='0000ff'
trackColour=[trackColourDefault,trackColourSet,
trackColourClear,trackColourOccupied,trackColourConflict]
tcDefault = 0
tcSet = 1
tcClear = 2
tcOccupied = 3
tcConflict = 4
signalColourSet='ff0000'
signalColourClear='00ff00'
signalColourConflict='0000ff'
signalColour=[signalColourSet,signalColourClear,signalColourConflict]
scSet=0
scClear=1
scConflict=2
These define the colours used on the svg diagram for track
segments and signal aspects. The signal aspects are
straightforward: only 2-aspect lights are used, red and
green ('set' and 'clear' respectively). A third aspect is
used only on the displayed svg diagram to indicate a signal in conflict
(blue). The signal aspect itself remains unchanged
(green).
The track segment colours are more subtle. Where the state
of a track routing is unknown, black is used. When the
points are set, and the route is known, the colour of the
track segment is changed to the 'trackSet' colout, a dark
green. When the signal controlling that track segment is
cleared, the track segment colour is set to a bright green
'trackClear'.
As with the signals, when a track segment routing is in
conflict with other settings, it is set to a conflict colour
'trackConflict' (also blue).
8.2 Stroke Colour Setting Routines
<svgLayout: define stroke colour setting routines 8.4> =#XLP svgLayout: define stroke colour setting routines
def getStrokeColour(object):
style=object.getAttribute('style')
res=strokePat.match(style)
if res:
prefix=res.group(1)
colour=res.group(2)
postfix=res.group(3)
return colour
def setStrokeColour(object,colour):
style=object.getAttribute('style')
res=strokePat.match(style)
if res:
prefix=res.group(1)
curcolour=res.group(2)
postfix=res.group(3)
new="%sstroke:#%s%s" % (prefix,colour,postfix)
object.setAttribute('style',new)
if 'tracks' in DEBUG:
print "setStroke sets new colour=%s" % (new)
pass
pass # end setStroke
8.3 The Layout class
<svgLayout: class SvgLayout 8.5> =
8.3.1 class SvgLayout initialization
<svgLayout: class SvgLayout initialization 8.6> =#XLP svgLayout: class SvgLayout initialization
def __init__(self,layfn,server='localhost'):
# get the layout
self.layoutFileName=layfn
try:
self.layoutFile=open(layfn,'r')
self.svgdom=parse(self.layoutFile)
# get the xml:base attribute
svg=self.svgdom.getElementsByTagName('svg')
# collect the first (and only) nodelist item
svg=svg.item(0)
base=svg.getAttributeNS('xml','base')
# and reset it to the incoming server name
uri="http://%s/~ajh/cgi-bin/jmr24/" % (server)
svg.setAttributeNS('xml','xml:base',uri)
self.layoutFile.close()
except IOError:
print "Cannot open the svg layout file %s" % (layfn)
sys.exit(1)
# map all svg paths into a dictionary of their ids
pathDict={}
self.allpaths=self.svgdom.getElementsByTagName('path')
for path in self.allpaths:
id=path.getAttribute('id')
pathDict[id]=path
self.pathDict=pathDict
self.pathKeys=pathDict.keys()
if 'load' in DEBUG:
print "<BR>Paths loaded:<UL>"
for k in self.pathKeys:
print "<LI>path key %s loaded</LI>" % (k)
print "</UL>/n"
# get all the track section data
trackf=open(trackfn,'r')
lines=trackf.readlines()
str=''
for l in lines:
str+=l
self.trackPaths=eval(str)
self.trackKeys=self.trackPaths.keys()
# get all the signal control data
sigconf=open(sigcontrolfn,'r')
lines=sigconf.readlines()
str=''
for l in lines:
str+=l
self.sigControls=eval(str)
self.sigconKeys=self.sigControls.keys()
pass
8.3.2 Get Layout Signals
<svgLayout: getLayoutSignals 8.7> =def getLayoutSignals(self):
''' set self.sigAspectsSvg to elements that contain the signal
aspects. These elements are used to indicate the signals colour
aspects. The value is constructed as a dictionary, indexed by
signal name (without the aspect character). '''
allpaths=self.allpaths
# filter out the signal aspect paths, indicated by ids of the form
# <region>s<number>a (s = signal, a=aspect)
self.sigAspectsSvg={}
for path in allpaths:
thisid=path.getAttribute('id')
res=re.match('(%s)s(%s)a' % (
regionPat,
numberPat),thisid)
if res:
if 'load' in DEBUG:
print "loading signal %s" % (thisid)
sig="%ss%s" % (res.group(1),res.group(2)) # remove the aspect letter
self.sigAspectsSvg[sig]=path
pass
8.3.3 Get Layout Points
<svgLayout: getLayoutPoints 8.8> =def getLayoutPoints(self):
''' set self.pntDirectionsSvg to elements that contain the point
directions. These elements are used to indicate the points
directions. The value is constructed as a dictionary, indexed by
point name (without the direction character). '''
allpaths=self.allpaths
# filter out the point direction paths, indicated by ids of the form
# <region>p<number><dir> (p = point)
self.pntDirectionsSvg={}
self.trackPathsvg={}
if 'tracks' in DEBUG: print "<P>trackKeys=%s" % (self.trackKeys)
for path in allpaths:
thisid=path.getAttribute('id')
res=re.match('(%s)p(%s)(%s)' % (
regionPat,
numberPat,directionPat),thisid)
if res:
if 'load' in DEBUG:
print "loading point %s" % (thisid)
# don't remove the direction letter
pnt="%sp%s%s" % (res.group(1),res.group(2),res.group(3))
self.pntDirectionsSvg[pnt]=path
pass
if 'tracks' in DEBUG: print "this path = %s" % (thisid)
if thisid in self.trackKeys:
paths=self.trackPaths[thisid]
if 'tracks' in DEBUG: print "<BR>checking paths %s" % (paths)
for pnt in paths:
if 'tracks' in DEBUG:
print "loading trackpath of %s at %s, " % (thisid,pnt)
self.trackPathsvg[pnt]=path
pass
if 'tracks' in DEBUG:
print "trackPathsvgKeys=%s" % (self.trackPathsvg.keys())
print "<P>"
8.3.4 Save Layout
<svgLayout: saveLayout 8.9> =def saveLayout(self,fileName):
outstring=self.svgdom.toxml()
svgOut=open(fileName,'w')
svgOut.write(outstring)
svgOut.close()
self.string=outstring
8.3.5 Set Signal
<svgLayout: setSignal 8.10> =def setSignal(self,sigId,aspect):
aspect=aspect.upper()
if 'signals' in DEBUG:
print "<BR>setSignal(%s,%s)" % (sigId,aspect)
try:
path=self.sigAspectsSvg[sigId]
except KeyError:
print "KeyError: available keys are %s" % (self.sigAspectsSvg.keys())
style=path.getAttribute('style')
res=re.match('fill:#([0-9a-f]+)(.*)$',style)
if res:
colour=res.group(1)
rest=res.group(2)
if aspect=='R':
colour=signalColour[scSet]
elif aspect=='G':
colour=signalColour[scClear]
elif aspect=='B':
colour=signalColour[scConflict]
new="fill:#%s%s" % (colour,rest)
path.setAttribute('style',new)
if 'signals' in DEBUG:
print "setSignal sets new style=%s" % (new)
else:
print "Cannot find style in path %s" % (path)
return
This routine is now only responsible for setting the signal
aspect. No other track logic is required, that all having
been moved to jmr.py in version 2.3.1.
8.4 Set Points
<svgLayout: setPoints 8.11> =def setPoint(self,pntId,dir):
pass
# there is nothing for this routine to do in version 2.3.1
All the logic for determining the colour of the track
segments controlled by a point has now been moved to
jmr.py. This is now a stub for compatibility
reasons.
8.5 svgLayout: setSegments
<svgLayout: setSegments 8.12> =def setPathColour(self,path,colour):
style=path.getAttribute('style')
res=re.match('(.*)stroke:#([0-9a-f]+)(.*)$',style)
if res:
prefix=res.group(1)
curcol=res.group(2)
postfix=res.group(3)
new="%sstroke:#%s%s" % (prefix,colour,postfix)
path.setAttribute('style',new)
return res
<svgLayout: setSegments 8.13> =def setSegment(self,segname,state):
if 'segments' in DEBUG:
print "setSegment(%s,%s)" % (segname,state)
if not self.pathDict.has_key(segname):
if segname[1]!='s':
# ignore segment if signal
if 'segments' in DEBUG:
print "setSegment cannot find segment for %s" % segname
return
path=self.pathDict[segname]
colour=trackColour[tcDefault]
if state=='S':
# segment is "set"
colour=trackColour[tcSet]
elif state=='C':
# segment is "clear"
colour=trackColour[tcClear]
elif state=='O':
# segment is "occupied"
colour=trackColour[tcOccupied]
elif state=='B':
# segment is "bad"
colour=trackColour[tcConflict]
res=self.setPathColour(path,colour)
if not res:
print "Unable to set track segment %s" % (segname)
setSegment now (in version 2.3.1) assumes only the
responsibility for setting the displayed track colour. It
is done on a per-segment basis, and no surrounding track
segments are affected by this routine.
9. Help Text
"intro.html" 9.1 =<P>You can set the points and signals by specifying parameters to this
program. These are in the form of standard cgi queries, viz
<PRE>http://dimboola.ajh.co/~ajh/cgi-bin/jmr2.py&action&action&...</PRE>
where each <TT>action</TT> is as follows:
<UL>
<LI>help (this listing)</LI>
<LI><region>s<number>={r|R|g|G}
Set signal number in region to aspect given</LI>
<LI>points: list current point settings</LI>
<LI>signals: list current signal settings</LI>
<LI>redraw: redraw the current layout (clears any messages)</LI>
<LI>reset: return all signals to red</LI>
<LI>route=<route-name>[,<path-name>]:
set a route and (optional) signalling path
</UL>
<DL>Regions are:
<DT style="font-weight:bold;margin-left:2em">c|C</DT><DD>Cabernet</DD>
<DT style="font-weight:bold;margin-left:2em">g|G</DT><DD>Grenache</DD>
<DT style="font-weight:bold;margin-left:2em">m|M</DT><DD>Merlot</DD>
<DT style="font-weight:bold;margin-left:2em">p|P</DT><DD>Pinot</DD>
<DT style="font-weight:bold;margin-left:2em">s|S</DT><DD>Shiraz</DD>
</DL>
<DL>Routes are:
<DT style="font-weight:bold;margin-left:2em">inner</DT>
<DD>The inner continous loop</DD>
<DT style="font-weight:bold;margin-left:2em">outer</DT>
<DD>The outer continuous loop</DD>
</DL>
</DL>
<DL>Paths are:
<DT style="font-weight:bold;margin-left:2em">clock</DT>
<DD>set signals for clockwise travel</DD>
<DT style="font-weight:bold;margin-left:2em">anti</DT>
<DD>Set signals for anti-clockwise</DD>
</DL>
</P>
<P>So, for example
<PRE>http://ajh.co/~ajh/cgi-bin/jmr2.py&points&gp1=r&gs1=g&signals</PRE>
will first show the current points settings, then set points gp1 to
right, and signals gs1 to green, and finally display the current
signals settings.</P>
<P>Note that conflicting movements are not allowed: you cannot alter any point
unless the controlling signals are red, not can you set signals to green if
that would conflict with any current points or signals settings.
</P>
<P><B>Currently only the regions <I>Cabernet</I> and <I>Grenache</I>
are completed</B>.
</P>
10. Setup of Server
To setup the apache server to handle the above scripts,
you need to do the following:
- Install the apache2 server. On my system, this
required
apt-get install apache2
Answer 'Y' to the question Do you want to continue [Y/n]?
-
Edit the file /etc/apache2/apache2.conf to include
the following lines:
#
# UserDir: The name of the directory that is appended onto a user's home
# directory if a ~user request is received. Note that you must also set
# the default access control for these directories, as in the example below.
#
UserDir www
# enable cgi directories
<Directory /home/*/www/cgi-bin/>
Options ExecCGI
SetHandler cgi-script
</Directory>
then restart the apache server:
apache2ctl restart
This will enable the user sub-directories www to
contain each user's public HTML, and enable cgi scripts to
run from the directory www/cgi-bin beneath them.
11. Support Programs
This section describes a number of programs developed to
facilitate the maintenance of the data structures used by the
JMR system. Because those data structures contain finicky
Python syntax, it was found to be tedious to write them all by
hand, and this first program was developed to take the tedium
out of maintaining the data tables for
signalControls.py.
11.1 The walkNetwork Program
This program is designed to traverse every path in the
network, in order to establish the integrity of the network
data. It does this by traversing the paths, both clockwise and
anticlockwise, accessible from every track segment in the
network, and generating a list of such paths.
The algorithm is recursive. Given a track segment and a
direction to traverse, it follows each path in the network
(regardless of how the points are currently thrown) until it
either finds a closed loop (return to the original segment),
or it finds a change of direction in the orientation of the
track segment (indicated by the path returning to the
just-visited segment). In words, this is:
- If the segment is in the path already generated, a loop
is identified and the walk terminates.
- If the segment being visited has as its next segment the
segment just visited, a change of direction is indicated and
the walk terminates.
- If the next segment has multiple choices, it is a point,
and two (or three) subbranches of the walk are generated.
"walkNetwork.py" 11.1 =#!/usr/bin/python
import LayoutComponents
def explore(l,tr,sofar,dir):
print "explore(l,%s,%s,%s)" % (tr,sofar,dir)
retval=[]
if not tr:
return sofar
if tr in sofar:
sofar.append(tr)
return sofar
sofar.append(tr)
if dir=='C':
track=l.tracks[tr].clock
else:
track=l.tracks[tr].anti
if not track:
return sofar
next=track[0]
return explore(l,next,sofar,dir)
def main():
layout=LayoutComponents.Layout()
layout.load()
keys=layout.tracks.keys()
keys.sort()
for track in keys:
for dirn in ['C','A']:
print "exploring track %s in %s direction" % (track,dirn)
res=explore(layout,track,[],dirn)
print res
print
if __name__ == '__main__':
main()
11.2 makeJmrData.py
The makeJmrData.py program builds the two files
signalControls.py and pointsConflicts.py
required by the interlocking.py module. The first file
contains definitions for all the trck sections between two
following signals, and the second defines those signals (and
the path) that must be reset before a given set of points may
be thrown.
The program takes a definition of the layout in a
user-friendly form, and converts it to a synatctically correct
python definition of the two tables. Here is an EBNF
definition of the input language:
Line = (Signal '-' ControlList) | (Point '-' AlternatePath) .
ConflictLine = Signal '-' AlternatePath
Signal = Area 's' Number .
Area = 'C' | 'G' | 'M' | 'P' | 'S' .
Number = ('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9')+ .
Track = Area 't' Number .
ControlList = Path [',' Path] .
Path = Signal | PointPath | Track | AlternatePath .
PointPath = Area 'p' Number [ Dir ] .
Dir = 'L' | 'R' .
AlternatePath = '(' Guard [',' ControlList] ')' ['/' AlternatePath] .
Guard = PointPath .
It uses a recursive descent approach to parsing the input source.
"makeJmrData.py" 11.2 =import copy
import getopt
import re
import sys
import types
DEBUG=[]
SHOWPNTS=False
areacode='C G M P S'.split()
aspects='R G'.split()
digits='0 1 2 3 4 5 6 7 8 9'.split()
direction='L R C'.split()
blocksfile=''
controlfile=''
conflictfile=''
segmentfile=''
blocktable={}; sigtable={};pnttable={};conflicts={};segments={}
def extendAlternates(x,y):
# make x a prefix of all elements of y
r=[[] for i in range(len(y))]
i=0
for p in r:
for q in x:
p.append(q)
p.extend(y[i])
i+=1
return r
<makeJmrData: recursive descent routines 11.3>
def DefinePointsTable(l):
if 'signals' in DEBUG: print "\n\nprocessing %s" % (l)
res=re.match('(.)(.)(\d+)-(.*)$',l)
if res:
area=res.group(1).upper()
sig=res.group(2)
num=res.group(3)
c=res.group(4)
if sig=='s':
print "Error: signal found in points table (%s)" % (l)
elif sig=='p':
point="%s%s%s" % (area,sig,num)
if 'signals' in DEBUG: print "matches point %s" % (point)
(pathway,c)=AlternatePath(c,0)
if 'signals' in DEBUG: print "returns alternate paths %s" % (pathway)
pnttable[point]=pathway
else:
print "Error: cannot match line %s" % (l)
def DefineSignalsTable(l):
if 'signals' in DEBUG: print "\n\nprocessing %s" % (l)
res=re.match('(.)(.)(\d+)-(.*)$',l)
if res:
area=res.group(1).upper()
sig=res.group(2)
num=res.group(3)
c=res.group(4)
if sig=='s':
signal="%s%s%s" % (area,sig,num)
if 'signals' in DEBUG: print "matches signal %s" % (signal)
(pathway,c)=ControlList(c,0)
if type(pathway[0]) is not types.ListType:
pathway=[pathway]
if 'signals' in DEBUG: print "returns control list %s" % (pathway)
sigtable[signal]=pathway
elif sig=='p':
print "Error: point found in signals table"
else:
print "Error: cannot match line %s" % (l)
def DefineBlocksTable(l):
if 'blocks' in DEBUG: print "\n\nprocessing %s" % (l)
res=re.match('(.)(.)(\d+)-(.)(.)(\d+):(.*)$',l)
if res:
area1=res.group(1).upper()
sig1=res.group(2)
num1=res.group(3)
area2=res.group(4).upper()
sig2=res.group(5)
num2=res.group(6)
c=res.group(7)
if sig1=='s' and sig2=='s':
signal1="%s%s%s" % (area1,sig1,num1)
signal2="%s%s%s" % (area2,sig2,num2)
blockname="%s-%s" % (signal1,signal2)
if 'blocks' in DEBUG: print "matches block %s" % (blockname)
(pathway,c)=ControlList(c,0)
if 'blocks' in DEBUG: print "returns control list %s" % (pathway)
blocktable[blockname]=pathway
else:
bad=sig1
if bad=='s': bad=sig2
print "Error: bad component %s is not a signal, found at %s" % (bad,l)
else:
print "Error: cannot match line %s" % (l)
pass
<makeJmrData: main routine 11.4,11.5,11.6,11.7,11.8,11.9,11.10,11.11>
if __name__ == '__main__':
(vals,path)=getopt.getopt(sys.argv[1:],'bdcpstfg',
['blocks=','debug','debcon','points','signals','controls=','conflicts=','segments='])
for (opt,val) in vals:
if opt=='-d' or opt=='--debug':
DEBUG.append('debug')
elif opt=='-c' or opt=='--debcon':
DEBUG.append('debcon')
elif opt=='-p' or opt=='--points':
DEBUG.append('points')
elif opt=='-s' or opt=='--signals':
DEBUG.append('signals')
elif opt=='-b':
blocksfile='blocksData.py'
elif opt=='--blocks':
blocksfile=val
elif opt=='-f':
conflictfile='pointConflicts.py'
elif opt=='--conflicts':
conflictfile=val
elif opt=='-t':
controlfile='signalControls.py'
elif opt=='--controls':
controlfile=val
elif opt=='-g':
segmentfile='segmentData.py'
elif opt=='--segments':
segmentfile=val
if len(path)>0:
srcfile=path[0]
else:
srcfile='jmrData.txt'
DEBUG.append('blocks')
main(srcfile)
11.3 makeJmrData: recursive descent routines
<makeJmrData: recursive descent routines 11.3> =def Area(c):
if c[0].upper() in areacode:
return (c[0].upper(),c[1:])
else:
print "Error: \"%s\" does not start with area code" % (c)
return ('',c)
def Number(c):
if c[0] in digits:
i=0
while i<len(c) and c[i] in digits: i+=1
return (c[0:i],c[i:])
else:
print "Error: \"%s\" does not start with number" % (c)
return ('',c)
def Aspect(c):
if c[0].upper() in aspects:
return (c[0].upper(),c[1:])
else:
print "Error: \"%s\" does not start with aspect" % (c)
return ('',c)
def Signal(c):
if DEBUG: print "Signal(%s)" % c
(sig,c)=Area(c)
if c[0]=='s':
c=c[1:]
else:
print "Error: \"%s\" does not start with signal flag" % (c)
(num,c)=Number(c)
if c and c[0].upper() in aspects:
(asp,c)=Aspect(c)
sig=sig+'s'+num+asp
if DEBUG: "Signal(...) => (%s,%s)" % (sig,c)
return (sig,c)
else:
sig=sig+'s'+num
if DEBUG: "Signal(...) => (%s,%s)" % (sig,c)
return (sig,c)
def Track(c):
(trk,c)=Area(c)
if c[0]=='t':
c=c[1:]
else:
print "Error: \"%s\" does not start with track flag" % (c)
(num,c)=Number(c)
return (trk+'t'+num,c)
def Dir(c):
if c[0].upper() in direction:
return (c[0].upper(),c[1:])
else:
print "Error: \"%s\" does not start with direction" % (c)
return ('',c)
def PointPath(c):
(pnt,c)=Area(c)
if c[0]=='p':
c=c[1:]
else:
print "Error: \"%s\" does not start with points flag" % (c)
(num,c)=Number(c)
if c and c[0].upper() in direction:
(dir,c)=Dir(c)
return (pnt+'p'+num+dir,c)
else:
pntkey=pnt+'p'+num
if pnttable.has_key(pntkey):
pth=pnttable[pntkey]
return (pth,c)
else:
# forward reference, return just the point id
if 'signals' in DEBUG:
print "PointPath returns forward reference %s" % (pntkey)
return (pntkey,c)
def Guard(c):
if DEBUG: print "Guard(%s)" % c
if c[1]=='p':
(grd,c)=PointPath(c)
if DEBUG: "Guard(...) => (%s,%s)" % (grd,c)
return (grd,c)
elif c[1]=='s':
(sig,c)=Signal(c)
if DEBUG: "Guard(...) => (%s,%s)" % (sig,c)
return (sig,c)
else:
print "Error: guard %g must be a point or signal" % (grd)
return (None,c)
def AlternatePath(c,lev):
ind='. '*lev
if DEBUG: print "%sAlternatePath(%s)" % (ind,c)
pathlist=[]
dict={}
if c[0]=='(':
(grd,c)=Guard(c[1:])
path=[grd]
if c[0]==',':
(pth,c)=ControlList(c[1:],lev+1)
if type(pth[0]) is types.ListType:
if DEBUG: print "%sAlternatePath prefixs path %s with %s" % (ind,pth,path)
path=extendAlternates(path,pth)
if DEBUG: print "%sAlternatePath computes an extended path %s" % (ind,path)
pathlist.extend(path)
else:
if DEBUG: print "%sAlternatePath has path that is not list of lists %s" % (ind,pth)
path.extend(pth)
pathlist.append(path)
if DEBUG:
print "%sAlternatePath computes pathlist %s" % (ind,pathlist)
else:
pathlist.append(path)
if c and c[0]==')':
c=c[1:]
if c!='' and c[0]=='/':
if DEBUG:
print "%sgoing round for another alternate with >%s<" % (ind,c)
(alt,c)=AlternatePath(c[1:],lev+1)
path=[]
if DEBUG:
print "%sgot another alternate path %s leaving >%s<" % (ind,alt,c)
if alt:
for e in alt:
#pathlist=extendAlternates(e,pathlist)
pathlist.append(e)
if DEBUG:
print "%sappended %s to pathlist=%s" % (ind,e,pathlist)
if DEBUG:
print "%sfull path=%s" % (ind,pathlist)
else:
print "%sError: bad alternate path syntax in %s" % (ind,c)
#if c and c[0]==')':
# c=c[1:]
if DEBUG:
print "%sAlternatePath returns=%s" % (ind,pathlist)
return (pathlist,c)
def Path(c,lev):
ind='. '*lev
if 'signals' in DEBUG: print "%sPath(%s)" % (ind,c)
pth=''
if c[0]=='(':
(alt,c)=AlternatePath(c,lev+1)
pth=alt
elif c[1]=='s':
(sig,c)=Signal(c)
pth=sig
elif c[1]=='p':
(pnt,c)=PointPath(c)
pth=pnt
elif c[1]=='t':
(trk,c)=Track(c)
pth=trk
else:
print "%sError: no path at \"%s\"" % (ind,c)
if 'signals' in DEBUG:
print "%sPath returns %s (leaving %s)" % (ind,pth,c)
return (pth,c)
def ControlList(c,lev):
ind='. '*lev
if 'signals' in DEBUG: print "%sControlList(%s)" % (ind,c)
paths=[]
(pth,c)=Path(c,lev+1)
if type(pth[0]) is types.ListType:
paths=pth
else:
paths.append(pth)
while c!='' and c[0]==',':
c=c[1:]
(pth,c)=Path(c,lev+1)
if type(pth[0]) is types.ListType:
paths=extendAlternates(paths,pth)
if DEBUG: print "%sControlList extends paths to %s" % (ind,paths)
else:
paths.append(pth)
if 'signals' in DEBUG:
print "%sControlList returns %s (leaving %s)" % (ind,paths,c)
return (paths,c)
11.4 makeJmrData: main routine
<makeJmrData: main routine 11.4> =def main(srcname):
definer=DefinePointsTable # in lieu of anything else
f=open(srcname,'r')
lines=f.readlines()
for l in lines:
l=l.strip()
if l=='': continue
if l[0]=='#':
# skip blanks
i=1
while l[i]==' ': i+=1
# check first two non-blank chars for processing type
if l[i:i+2]=='si':
if DEBUG: print "switching to signals"
definer=DefineSignalsTable
elif l[i:i+2]=='po':
if DEBUG: print "switching to points"
definer=DefinePointsTable
elif l[i:i+2]=='bl':
if DEBUG: print "switching to blocks"
definer=DefineBlocksTable
else:
# other comment
pass
continue
# process the line
if DEBUG: print "\n\n**** processing %s" % (l)
definer(l)
The main routine is responsible for reading lines of layout
definition, and switching between the points definition and
signals definition. Note that this is not strictly necessary,
but is a hangover from an earlier design. (It should be
removed at some stage.) We handle this switching by using a
different definer routine.
<makeJmrData: main routine 11.5> = if 'points' in DEBUG:
keys=pnttable.keys()
keys.sort()
print "{"
for k in keys:
print " '%s' : %s," % (k,pnttable[k])
print "}"
If we want to see what the points table definition is, we can
display it by adding the --points flag to the CLI call.
Note that this points table is not used directly by the
jmr.py program, and is normally used only to analyse
the control paths and conflict paths. Hence it is not
normally required to be output.
<makeJmrData: main routine 11.6> = if controlfile:
cf=open(controlfile,'w')
print "Writing controls to file %s" % controlfile
else:
cf=sys.stdout
keys=sigtable.keys()
keys.sort()
cf.write("{\n")
for k in keys:
cf.write(" '%s' : %s,\n" % (k,sigtable[k]))
cf.write("}\n")
if cf!=sys.stdout:
cf.close()
Output the control table. This table defines the track
sections that are cleared when a given signal is cleared. If
no file is given for this table, it is written to standard
output.
<makeJmrData: main routine 11.7> = pntkeys=pnttable.keys()
pntkeys.sort()
sigkeys=sigtable.keys()
sigkeys.sort()
conflicts={}
for k in sigkeys:
if 'debcon' in DEBUG:
print "\nchecking signal %s - segment list=%s" % (k,sigtable[k])
pathlist=sigtable[k]
for path in pathlist:
pathtohere=[k+'G']
for p in path:
if 'debcon' in DEBUG:
print "\nchecking signal %s - path=%s" % (k,p)
pathtohere.append(p)
res=re.match('(.)(.)(\d+)(.)?',p)
if res:
a=res.group(1)
t=res.group(2)
n=res.group(3)
d=res.group(4)
if not d: d=''
if 'debcon' in DEBUG:
print "got a track seg = %s%s%s%s" % (a,t,n,d)
if t=='p':
pnt=a+t+n
if pnt in pntkeys:
if 'debcon' in DEBUG:
print "possible conflict between signal %s and point %s - conflict path = %s" % (k,pnt,pathtohere)
if conflicts.has_key(pnt):
conflicts[pnt].append(pathtohere)
else:
conflicts[pnt]=[pathtohere]
Analyse the conflicts in the layout.
<makeJmrData: main routine 11.8> = if conflictfile:
cf=open(conflictfile,'w')
print "Writing conflicts to file %s" % conflictfile
else:
cf=sys.stdout
cf.write("{\n")
for pnt in pntkeys:
if pnt in conflicts.keys():
cf.write(" '%s' : [%s,\n" % (pnt,conflicts[pnt][0]))
for i in range(1,len(conflicts[pnt])):
cf.write(" %s,\n" % (conflicts[pnt][i]))
cf.write(" ],\n")
else:
cf.write("No key %s in conflicts table" % pnt)
cf.write("}\n")
if cf!=sys.stdout:
cf.close()
Output the conflicts table. This table defines whether a
given point may be altered. It can only be altered when all
of the controlling signals of the various sections that it is
in are red. If no file is given for this table, it is written
to standard output.
<makeJmrData: main routine 11.9> = sigkeys=sigtable.keys()
sigkeys.sort()
for k in sigkeys:
if DEBUG:
print "\nanalysing signal %s - segment list=%s" % (k,sigtable[k])
pathlist=sigtable[k]
for path in pathlist:
for p in path:
if DEBUG:
print "\nchecking signal %s - path=%s" % (k,p)
res=re.match('(.)(.)(\d+)(.)?',p)
if res:
a=res.group(1)
t=res.group(2)
n=res.group(3)
d=res.group(4)
if not d: d=''
if DEBUG:
print "got a track seg = %s%s%s%s" % (a,t,n,d)
if t=='p':
pnt=a+t+n+d
segments[pnt]='X'
if t=='t':
seg=a+t+n
segments[seg]='X'
Analyse the track segments in the layout.
<makeJmrData: main routine 11.10> = if segmentfile:
cf=open(segmentfile,'w')
print "Writing segments to file %s" % segmentfile
else:
cf=sys.stdout
cf.write("{\n")
keys=segments.keys()
keys.sort()
area='C'; type='p'
for seg in keys:
if area!=seg[0] or type!=seg[1]:
cf.write("\n")
area=seg[0]; type=seg[1]
cf.write(" '%s':'%s'," % (seg,segments[seg]))
cf.write("\n}\n")
if cf!=sys.stdout:
cf.close()
Output the segments table. This table defines all the track
segments and their current state:
- X
- current state undefined or unknown. Show as black segment.
- S
- current state is "set", meaning that it is part of a
route that has been or is being set, ready for clearing.
Show as dark green.
- C
- current state is "clear", meaning that it is part of a
route that has been set and cleared, through clearing a
controlling signal for the route.
Show as bright green.
If no file is given for this table, it is written
to standard output.
<makeJmrData: main routine 11.11> = if blocksfile:
cf=open(blocksfile,'w')
print "Writing blocks to file %s" % blocksfile
blockkeys=blocktable.keys()
blockkeys.sort()
cf.write("{\n")
for key in blockkeys:
cf.write(" %s : %s,\n" % (key,blocktable[key]))
cf.write("}\n")
if cf!=sys.stdout:
cf.close()
Analyse and output the block table for the layout. If no
file is specified for this data, it is written to standard
output. Two flags are available to specify that the data is
written to a file:
- -b
- write the block data to a file called
blockData.py.
- --blocks=blockfilename
- write the data to a file given as blockfilename.
12. The Arduino Programs
12.1 The Points Driver Arduino
"pointmotorserial.pde" 12.1 =/*
Test program for PCF8574 I2C I/O expander
Used to drive point motor solenoids
Each alternate port is set high and low, and then reversed after 2 seconds
Note that the read operations currently do not work, due to missing pull-ups.
*/
#include <Wire.h>
uint8_t expander = B0100001; // Address with xxx is b0100xxx
// Note that the R/W bit is not part of this address.
void setup() {
Wire.begin();
expanderWrite(B11111111);
Serial.begin(115200);
}
void pointMotor(byte pm) {
expanderWrite(~(1 << pm));
delay(100);
expanderWrite(0xff);
}
void loop() {
// check if data has been sent from the computer:
if (Serial.available()) {
// read the most recent byte (which will be from 0 to 255):
int pin = Serial.read();
if(pin >= '0' && pin <= '7') {
Serial.print("Read: ");
Serial.println(pin);
pin -= '0';
pointMotor(pin);
Serial.println(expanderRead(), BIN);
}
}
Serial.print("Writing B00000000 to ");
Serial.println(expander, BIN);
expanderWrite(0x0a);
Serial.print("Read 1: ");
Serial.println(expanderRead(), BIN);
delay(50);
expanderWrite(0xff);
delay(900);
Serial.print("Writing B11111111 to ");
Serial.println(expander, BIN);
expanderWrite(0x05);
Serial.print("Read 2: ");
Serial.println(expanderRead(), BIN);
delay(50);
expanderWrite(0xff);
delay(900);
Serial.print("Writing B00000000 to ");
Serial.println(expander, BIN);
expanderWrite(0xfa);
Serial.print("Read 1: ");
Serial.println(expanderRead(), BIN);
delay(50);
expanderWrite(0xff);
delay(900);
Serial.print("Writing B11111111 to ");
Serial.println(expander, BIN);
expanderWrite(0xf5);
Serial.print("Read 2: ");
Serial.println(expanderRead(), BIN);
delay(50);
expanderWrite(0xff);
delay(900);
}
void expanderWrite(byte _data ) {
Wire.beginTransmission(expander);
Wire.send(_data);
Wire.endTransmission();
}
byte expanderRead() {
byte _data=0x53;
Wire.requestFrom(expander, (byte)1);
if(Wire.available()) {
_data = Wire.receive();
}
return _data;
}
13. TODOs
- (20150830:152626) When Ps02 is cleared, and Cp04 thrown
right, Cs07 cannot be cleared. If you do it in the order of
clearing Cs07 first, then Ps02, it is OK.
- (20151011:182233) If a block is cleared, and then the
controlling signal reset, the block must be uncleared (set),
and removed from the list of cleared blocks.
14. Makefile
"Makefile" 14.1 =# define all the (generated) files to be removed on make clean
GenFiles=$(EMPTY)
ARDUINO-POINTS=/home/ajh/Personal/Pastimes/ModelRailways/Arduino/pointmotorserial
include ${HOME}/etc/MakeXLP
HOME = /home/ajh
CGI = $(HOME)/www/cgi-bin/jmr24/
WORLD = ajh.co:www/cgi-bin/jmr24/
XSLLIB=/home/ajh/lib/xsl
XSLFILES=$(XSLLIB)/lit2html.xsl $(XSLLIB)/tables2html.xsl
jmr.tangle: jmr.xlp interlocking.xlp makeJmrData.xlp svgLayout.xlp
tangle: jmr.tangle
jmr.py: jmr.tangle
trains.py: tangle
interlocking.py: interlocking.tangle
makeJmrData.py: makeJmrData.tangle
install: install-local install-fatcon
install-local: jmr.tangle
chmod 755 jmr.py
cp -p jmr.py $(CGI)/
cp -p LayoutComponents.py $(CGI)/
cp -p svgLayout.py $(CGI)/
# cp -p pointsModule.py $(CGI)/
cp -p blocksData.py $(CGI)/
cp -p routesData.py $(CGI)/
install-world: jmr.tangle
chmod 755 jmr.py
rsync -auv jmr.py $(WORLD)/
rsync -auv LayoutComponents.py $(WORLD)/
rsync -auv svgLayout.py $(WORLD)/
# rsync -auv pointsModule.py $(WORLD)/
rsync -auv blocksData.py $(WORLD)/
rsync -auv routesData.py $(WORLD)/
install-fatcon: install-fatcon-trains install-fatcon-modules
install-fatcon-trains: jmr.tangle
rsync -aui trains.py trains@fat-controller:$(CGI)/trains.py
install-fatcon-modules: jmr.tangle
rsync -aui jmrData.py trains@fat-controller:$(CGI)/
rsync -aui pointsModule.py trains@fat-controller:$(CGI)/
chmod 666 pointsData.py
rsync -aui pointsData.py trains@fat-controller:$(CGI)/
chmod 666 signalsData.py
rsync -aui signalsData.py trains@fat-controller:$(CGI)/
rsync -aui pointsDriverData.py trains@fat-controller:$(CGI)/
rsync -aui intro.html trains@fat-controller:$(CGI)/
install-test: jmr.tangle
chmod 755 trains.py
cp -p trains.py $(CGI)/
cp -p jmrData.py $(CGI)/
cp -p pointsModule.py $(CGI)/
chmod 666 pointsData.py
cp -p pointsData.py $(CGI)/
cp -p pointsDriverData.py $(CGI)/
chmod 666 signalsData.py
cp -p signalsData.py $(CGI)/
cp -p conflictsData.py $(CGI)/
cp -p routesData.py $(CGI)/
install-arduino: jmr.tangle
cp -p pointmotorserial $(ARDUINO-POINTS)/pointmotorserial.pde
clean: litclean
-rm $(GenFiles)
# legacy only beyond this point
#default=program
#GenFiles = .program install-program
install-layout: jmr.tangle
rsync -auv libjmr.py dimboola:/Users/ajh/$(CGI)/libjmr.py
touch install-layout
local-layout: $(CGI)/libjmr.py
$(CGI)/libjmr.py: jmr.tangle
cp libjmr.py /Users/ajh/$(CGI)/libjmr.py
#install: install-trains
local: local-layout
install-trains: trains.py
rsync -aui trains.py trains@fat-controller:$(CGI)/trains.py
install-points: points.py
rsync -aui points.py trains@fat-controller:$(CGI)/points.py
install-signals: signals.py
rsync -aui signals.py trains@fat-controller:$(CGI)/signals.py
uninstall:
rsync -aui trains@fat-controller:$(CGI)/trains.py ./trains.py
all: install local
15. Indices
15.1 Identifiers
Identifier |
Defined in |
Used in |
closePoints |
6.7 |
|
directionPat |
2.1 |
6.1, 7.1, 8.2
|
directionPat |
6.1 |
6.1, 7.1, 8.2
|
directionPat |
7.1 |
6.1, 7.1, 8.2
|
directionPat |
8.2 |
6.1, 7.1, 8.2
|
getPath |
3.15 |
3.11, 3.14, 3.15, 3.15, 3.15, 3.15, 3.15, 3.15, 3.16, 3.18, 3.18
|
jmrData |
5.1 |
5.1, 5.1, 6.1, 6.1, 6.1, 6.1, 7.1, 7.1, 7.1, 7.1, 8.2, 8.2, 8.2, 8.2
|
loadPoints |
6.2 |
|
numberPat |
2.1 |
4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
numberPat |
5.1 |
4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
numberPat |
6.1 |
4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
numberPat |
7.1 |
4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
numberPat |
8.2 |
4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
pointsModule |
5.1 |
|
regionPat |
2.1 |
4.3, 4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
regionPat |
5.1 |
4.3, 4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
regionPat |
6.1 |
4.3, 4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
regionPat |
7.1 |
4.3, 4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
regionPat |
8.2 |
4.3, 4.10, 5.1, 5.1, 6.1, 7.1, 7.14, 8.2, 8.7, 8.8
|
15.2 Macros
Chunk Name |
Defined in |
Used in |
LayoutComponents: Layout class: changeSignal method |
3.13 |
3.10 |
LayoutComponents: Layout class: checkInterlocking method |
3.11 |
3.10 |
LayoutComponents: Layout class: collectSignals method |
3.16 |
3.10 |
LayoutComponents: Layout class: getPath method |
3.15 |
3.10 |
LayoutComponents: Layout class: setClearPath method |
3.14 |
3.10 |
LayoutComponents: Layout class: trackProtected method |
3.17 |
3.10 |
LayoutComponents: Point class |
3.4 |
3.1 |
LayoutComponents: Route class |
3.8 |
3.1 |
LayoutComponents: Signal class |
3.2 |
3.1 |
LayoutComponents: Track class |
3.6 |
3.1 |
LayoutComponents: the Layout class |
3.10 |
3.1 |
LayoutComponents: the Points class |
3.5 |
3.1 |
LayoutComponents: the Routes class |
3.9 |
3.1 |
LayoutComponents: the Signals class |
3.3 |
3.1 |
LayoutComponents: the Tracks class |
3.7 |
3.1 |
LayoutComponents: the main routine |
3.18 |
3.1 |
checkInterlocking: check all segments in path |
3.12 |
3.11 |
current date |
16.1.2 |
|
current version |
16.1.1 |
|
jmr: change signal or point |
4.11 |
4.10 |
jmr: close all files and windup |
4.16 |
4.1 |
jmr: collect web page parameters |
4.6 |
4.1 |
jmr: define subroutines |
4.5 |
4.1 |
jmr: extract the browser parameters and process them |
4.10 |
4.1 |
jmr: handle debug mode |
4.8 |
4.1 |
jmr: import modules |
4.2 |
4.1 |
jmr: initialize fat controller interfacing |
4.4 |
4.1 |
jmr: initialize globals |
4.3 |
4.1 |
jmr: initialize web page |
4.7 |
4.1 |
jmr: load all data variables |
4.9 |
4.1 |
jmr: perform help action |
4.12 |
4.10 |
jmr: perform reset action |
4.13 |
4.10 |
jmr: perform route action |
4.14 |
4.10 |
jmr: render the SVG layout diagram |
4.15 |
4.1 |
makeJmrData: main routine |
11.4, 11.5, 11.6, 11.7, 11.8, 11.9, 11.10, 11.11
|
11.2 |
makeJmrData: recursive descent routines |
11.3 |
11.2 |
svgLayout: class SvgLayout |
8.5 |
8.1 |
svgLayout: class SvgLayout initialization |
8.6 |
8.5 |
svgLayout: colour definitions |
8.3 |
8.2 |
svgLayout: define stroke colour setting routines |
8.4 |
8.1 |
svgLayout: getLayoutPoints |
8.8 |
8.5 |
svgLayout: getLayoutSignals |
8.7 |
8.5 |
svgLayout: import and globals |
8.2 |
8.1 |
svgLayout: saveLayout |
8.9 |
8.5 |
svgLayout: setPoints |
8.11 |
8.5 |
svgLayout: setSegments |
8.12, 8.13
|
8.5 |
svgLayout: setSignal |
8.10 |
8.5 |
16. Document History
20120523:114009 |
ajh |
1.0.0 |
initial version. This is a rewrite of the jmr suite |
20120918:130814 |
ajh |
2.0.0 |
rewrite interlocking mechanism |
20120919:134144 |
ajh |
2.0.1 |
rationalize www server and localhost (fat-controller) server
code
|
20120922:131048 |
ajh |
2.1.0 |
restructure a number of modules and clean up code
|
20130623:170755 |
ajh |
2.2.0 |
New points/signals r + www/cgi-bin/jmr23 route setting regime
|
20130704:162958 |
ajh |
2.2.1 |
further changes to svgLayout and signal controls
|
20130712:172855 |
ajh |
2.3.0 |
rewriting great slabs of interlocking, conflicts and
control table generation
|
20130716:105534 |
ajh |
2.3.1 |
add track segment data maintenance, and remove
significant program logic from svgLayout
|
20150218:170910 |
ajh |
2.3.2 |
Clarify heel and toe terminology, and correct errors
|
20150820:180516 |
ajh |
2.4.0 |
rewrite in more OO style |
20150822:120706 |
ajh |
2.4.1 |
break LayoutComponents into literate program chunks
|
20150827:142808 |
ajh |
2.4.2 |
first working version with reversing tracks |
20150829:162117 |
ajh |
2.4.3 |
cleaned up a few more names, and added missing
signals, points, tracks |
<current version 16.1.1> = 2.4.3
<current date 16.1.2> = 20150829:162117