/* CONTENTS - (Use g to access required sections) -- 1 The Main class holds everything together -- 2 Queues are needed for managing track-resource requests -- 3 TrackSegments are the basic geometric/topological unit of track -- 3.1 IRDetectors "see" trains -- 4 Junctions link up track segments -- 4.1 A plain junction connects two pieces of track -- 4.2 A bifurcation junction can have its direction set. -- 5 Trains run on TrackSegments -- 6 TrackBlocks are the unit of allocation of track resources -- 6.1.1 A RouteSpec is used to route a train through a block. -- 6.1.2 A TrainSpec records information about a train for a block -- 6.2 The TrackBlock class itself -- 6.2.1 bk.check_in(t) checks a train into a block -- 6.2.2 bk.check_out(t) checks a train out of a block -- 6.2.3 bk.accept(trainspec) accepts a train into a block -- 6.2.4 bk.request(train,bk_next) requests access to a block -- 6.3.1 The Free State -- 6.3.2 The Preparing State -- 6.3.3 The Expecting State -- 6.3.4 The Occupied States -- 6.3.4.1 The Blocked State -- 6.3.4.2 The Stopped State -- 6.3.4.3 The Unblocked State General Notes The design of the structure of this program is guided by the desire to keep it aligned with the actual embedded system that runs in the Embedded Systems Education Laboratory. So it is desirable at least to preserve a clear demarcation between classes that are just emulating hardware, and those that are emulating components that embody software. In particular, the Train class emulates actual trains, which in our case have no embedded processor. So a train does not, for example, "know" where it is supposed to go - only instances of the TrackBlock class know that. This accounts for our introduction of the TrainSpec class, which is used by the TrackSegment class to refer to trains. A correspondence between trains and train-specifications is established at system-initialisation time. Its preservation is one of the invariants of a correct system. Trains interact primarily with instances of the TrackSegment class which itself also represents "dumb" hardware. This program does not provide an entirely clean demarcation... */ import java.util.*; // import java.awt.*; /* -- 1 The Main class holds everything together ------------------------- */ class Main { static float dt; static float time; static float time() {return time;} final static int n_trains = 3; final static int n_juncts = 5; final static int n_ir = 5; static Data_simple data = new Data_simple(); static TrackBlock block = null; static void run() {time = time+dt;} static StringTokenizer body(String c) {StringTokenizer r = new StringTokenizer(c,"(),:; ",true); String s = (String)r.nextElement(); System.out.println(s); s = (String)r.nextElement(); System.out.println(s); if (!s.equals("(")) Main.error( "'(' expected, '" + s + "' found"); return r; } static void obey(String c) {StringTokenizer args = body(c); if (c.startsWith("BL")) block = new TrackBlock(args); else if (c.startsWith("AS")) block.add_seg(new TrackSegment(args)); else if (c.startsWith("AJ")) block.add_junction(new Junction_bif(args)); else if (c.startsWith("AI")) block.add_infrared(new IRDetector(args)); else if (c.startsWith("BE")) {if (block!=null) {block.start(); block = null; } } else System.out.println("error illegal command" + c); } public static void error(String msg) {System.out.println("ERROR: " + msg);} public static void main(String[] args) { while (true) { String cmd = data.read(); if (cmd==null) return; System.out.println("Command: " + cmd); if (cmd == null) return; obey(cmd); } } } class TrainItem extends Thread{ protected Hashtable data; // public static Hashtable parseDataLine(String line) { Hashtable hash = new Hashtable(5); if (line==null) return new Hashtable(0); StringTokenizer tok = new StringTokenizer(line,";"); while (tok.hasMoreTokens()) { // get the next name-value pair String str = tok.nextToken().trim(); int i = str.indexOf(":"); if (i == -1) hash.put(str,""); else { String name = str.substring(0, i).trim(); String value = str.substring(i+1).trim(); hash.put(name,value); } } return hash; } public static void check(String lab, StringTokenizer args) { String s; if (! args.hasMoreElements()) Main.error("ran off end of command, looking for " + lab); s = (String)args.nextElement(); System.out.println(s); if (! s.equals(lab)) Main.error( "'" + lab + "' expected, '" + s + "' found"); } // e.g. Angle:23) public static int get_int(String lab, StringTokenizer args) {int i; check(lab,args); check(":",args); i = new Integer((String)args.nextElement()).intValue(); System.out.println(i+""); return i; } public static Point get_point(String lab, StringTokenizer args) {int x,y; check(lab,args); check(":",args); x = new Integer((String)args.nextElement()).intValue(); System.out.println(x+""); check(",",args); y = new Integer((String)args.nextElement()).intValue(); System.out.println(y+""); return new Point(x,y); } public static void get_points(String lab, StringTokenizer args) { int x,y; while (true) {x = new Integer((String)args.nextElement()).intValue(); System.out.println(x+""); check(",",args); y = new Integer((String)args.nextElement()).intValue(); System.out.println(y+""); if ((args.nextElement()).equals(")")) return; } } public static Identifier get_ident(String lab, StringTokenizer args) { String s; check(lab,args); s = (String)args.nextElement(); System.out.println(s+""); return new Identifier(s); } } /* -- 2 Queues are needed for managing track-resource requests ----------- here's a crummy implementation of queues */ class Queue{ public Queue(int n) {contents = new Object [n];} Object contents[]; int n=0; Object next() { Object ob = contents [0]; for (int i=0;i| ---------------|--- \--|------------------------|------|------------- ---------------|--- <----S2------->|<-J1->|<------S3-------------->|<-J2->|<----S4------> A track segment is a representation of a length of track which will interface with a junction at each end. The ends of a segment are numbered 0,1. A segment belongs to a track-section (or block in rail terminology). The block is the unit of allocation of track resources - only one train may occupy a block. A segment has a geometric and a topological role. Topologically, segments are the edges of a graph, of which junctions are the nodes. The graph characterises the trackwork. Geometrically a segment serves as the basis for a system display. Currently, segments are just straight lines, which a train traverses. For something approaching geometrical realism, there will be more segments in a system than are topologically necessary. Each segment belongs to a track section, which is the unit of allocation of track-resources. A TrackSegment provides power, specified by an integer between -100 and +100, for the train on it. The convention is that if the power is positive then the train will move from the 0-end of the section towards the 1-end of the segment. A TrackSegment may have an infrared detector, which will serve to notify the TrackBlock thread that a train is at a particular point in the segment. A train entering a segment should check_in with the segment; a train leaving should check_out. */ class TrackSegment extends TrainItem {float length; Identifier ident; Junction j0,j1; TrackBlock block; TrackBlock block() {return block;} public String toString() {return "Seg:" + ident + "in BL:" + block.ident;} int power(){return block.power();} public TrackSegment(StringTokenizer args) {ident = get_ident("ID",args); get_points("ID",args); } IRDetector detector; public Junction junction_0() {return j0;} public Junction junction_1() {return j1;} public float length() {return length;} public IRDetector detector() {return detector;} public void check_in(Train t) {block.check_in(t);} public void check_out(Train t) {detector.write(false); block.check_out(t); } } /* -- 3.1 IRDetectors "see" trains --------------------------------------- An IRDetector is positioned on a track-segment. The -position- method returns the distance at which a detector is located from the start of a segment. det.read() will return true if a train is at the detector - trains are given extent from this point of view by the fact that they have length. Instances of the Train class actually set the value of the detector using the -write- method. */ class IRDetector extends TrainItem {Identifier ident; public IRDetector(StringTokenizer args) {Point point; int angle; ident = get_ident("ID",args); point = get_point("Point",args); angle = get_int("Angle",args); } boolean value; float pos; public float position() {return pos;} public void write(boolean v) {value = v;} public boolean read() {return value;} } /* -- 4 Junctions link up track segments --------------------------------- Track segments are linked up with junctions. Junctions are of at least two kinds, plain and bif (bifurcation). Later we will introduce blind junctions and reversing junctions. Blind junctions essentially terminate a piece of track. For a train to enter a blind junction is an error. Reversing junctions accomodate topological requirements which imply that with some track layouts (the wye for example) two 1-ends of two segments may meet at a junction, or two 0-ends. A junction has a 0-end and a 1-end. A junction has a boolean attribute "parity". If the parity is true then a train approaching in the positive direction from any segment at the 0-end of the junction will emerge at the 1-end of the junction (this rule does not hold for reversing junctions). Junction_plain +ve --> -----|----|----- seg0 seg1 parity true Junction_bif +ve --> bif seg10 /----|---------- / ---|---- seg0 \ \----|---------- seg11 parity true Junction_bif <-- +ve bif seg10 /----|---------- / ---|---- seg0 \ \----|---------- seg11 parity false The traverse method predicts from which segment a train entering a junction will exit. ???? more work needed here The method-call junct.traverse(sense) is used to map from a junction to the segment from which a train will exit the junction. The boolean parameter -sense- specifies whether the train is approaching in the positive direction or the negative direction. [we also need sense-reversal junctions for some purposes...] */ abstract class Junction extends TrainItem { Identifier ident; boolean parity; abstract public TrackSegment traverse(boolean sense); } /* -- 4.1 A plain junction connects two pieces of track ------------------ */ class Junction_plain extends Junction {TrackSegment seg0, seg1; public TrackSegment traverse(boolean sense) {return (sense == parity)?seg1:seg0;} } /* -- 4.2 A bifurcation junction can have its direction set. ------------- */ class Junction_bif extends Junction {TrackSegment seg0, seg10, seg11; boolean dir; public Junction_bif(StringTokenizer args) {Point p_open, p_closed; ident = get_ident("ID",args); p_open = get_point("Open",args); p_closed = get_point("Closed",args); } public void set(boolean d) {dir = d;} public TrackSegment traverse(boolean sense) {return (sense==parity)?(dir?seg10:seg11):seg0;} } /* -- 5 Trains run on TrackSegments -------------------------------------- A train occupies a segment (realistically, it may occupy more than one, since a train has extent). It progresses along the segment according to the power supplied to the segment, until it comes to one of the two junctions which terminate the segment. */ class Train extends TrainItem {int ident; float v; // velocity float d; // distance along segment float len; // length of train TrackSegment seg; // segment that it's on float t_prev; // previous time run was called int ident() {return ident;} public String toString(Train t) {return "Train:" + ident + " v=" + v + " d=" + d + " l=" +len + " Seg:" + seg.ident; } public void run() // simulate {v = seg.power(); // v determined by power supplied float t = Main.time(); // compute time increment float dt = t-t_prev; IRDetector det = seg.detector(); // infra-red detector for train t_prev = t; d = d + v*dt; // integrate diff eq. if (det!=null) // can train be seen by { float d_det = (det.position()); // the IR detector det.write(d > d_det && d-len < d_det); // if there is one? } // next check if train // is leaving the segment if (d<0) // in negative direction? {seg.check_out(this); seg = (seg.junction_0()).traverse(false);// traverse the junction seg.check_in(this); // and check in } else if (d>seg.length()) {seg.check_out(this); seg = (seg.junction_1()).traverse(true); seg.check_in(this); } } } /* -- 6 TrackBlocks are the unit of allocation of track resources ------ Real trains and our model trains both are allocated track resources in units of a -block-. The primary safety criterion is that only one train can occupy a block at any one time. The TrackBlock class is software that would be present in the realised embedded system, whereas that of the Train and TrackSection classes etc. is in effect a simulation of hardware. To support this distinction, we refer to trains by their -train-identifier- a small integer, and we also refer to junctions etc. by small integers. [We have simplified things in this program - a train can have at most two blocks allocated to it, while full-sized trains may have 3 or 4. Also there are circumstances in which more than one train is permitted in a block at once, though typically at low speed to allow two trains to be connected to form one as in Newhaven Connecticut. Most recent full-sized practice uses a "moving block" concept that increases traffic density. ] -- 6.1.1 A RouteSpec is used to route a train through a block. -------- Trains are referred to by their identifier, a small integer. This is used junction is an array, indexed by train-identifier, which provides a specification for setting the junctions required to route the train. power is the power level to be used. stop is the infra-red detector at which the train is to be stopped if the subsequent section is not available. */ class RouteSpec { TrackBlock block_next; int power ; int exit_detector; boolean junction[] ; public String toString() {return "RouteSpec: BK:" + block_next.ident + " pow:" + " exit:" + junction; } } /* -- 6.1.2 A TrainSpec records information about a train for a block----- */ class TrainSpec { public TrainSpec(int id, TrackBlock bk) {ident = id; previous = bk;} public int ident; public TrackBlock previous; public String toString() {return "TrainSpec: " + ident + " BL:" + previous.ident;} } /* -- 6.2 The TrackBlock class itself ------------------------------------ A TrackBlock has 6 possible discrete states, falling into 3 major state-classes, free, booked, occupied. free Not allocated to any train booked Allocated to a train but not occupied preparing Setting up the environment to receive a train expecting Waiting for the train to arrive occupied Occupied by a train blocked The next track block for the train is not yet available. unblocked The next track block for the train is available. stopped The next track block is not available, and the train is stopped at an IR detector, waiting for permission to proceed. */ class TrackBlock extends TrainItem { int ident; TrainSpec train = null; // the train in the block int power = 0; // power supplied to it final static int free = 10; // constants for the states final static int preparing = 21; final static int expecting = 22; final static int blocked = 31; final static int unblocked = 32; final static int stopped = 33; int state = free; // the state of the block boolean accepted = false; // can train leave this block? Queue q = new Queue(4); // trains waiting to enter // the bif-junctions of the block are accessed through this array: Junction junction[] = new Junction_bif[10]; IRDetector ir_detector[] = new IRDetector[5]; RouteSpec routing[] = new RouteSpec[10]; RouteSpec route = null; // routing for train boolean track_circuit = false; // true if train in block IRDetector detector[] = new IRDetector[10]; public TrackBlock(StringTokenizer args) {Point p; ident = get_int("ID",args); check(";",args); p = get_point("Loc",args); } void add_seg(TrackSegment s) { s.block = this; } void add_junction(Junction j) { junction[j.ident.minor] = j; } void add_infrared(IRDetector d) { detector[d.ident.minor] = d; } void track_switch(int i,boolean d) // operate junction i {((Junction_bif)(junction[i])).set(d);} int power() {return power;} public String toString() {return "BL:" + ident + train;} public void run() {while (true) {System.out.println(this.toString()); if (state == free) run_free(); else if (state == preparing) run_preparing(); else if (state == expecting) run_expecting(); else if (state == blocked) run_blocked(); else if (state == unblocked) run_unblocked(); else if (state == stopped) run_stopped(); else System.out.println("error: wrong state"); } } void set_power(int p) {power = p;} boolean detect_train() {return track_circuit;} /* -- 6.2.1 bk.check_in(t) checks a train into a block ------------------- When a train enters a segment belonging to a block it registers with the block. This has the effect of setting the track-circuit boolean to true. As a check on system functioning, we make sure that the block is in the expecting state, and that the train it is expecting is the actual train that is entering the block. */ void check_in(Train t) {track_circuit = true; if (state != expecting) Main.error( "train " + t + " checked in to block which was not expecting a train"); if (t.ident != train.ident) Main.error( "train " + t + " checked in to block which was expecting train " + train.ident); } /* -- 6.2.2 bk.check_out(t) checks a train out of a block ---------------- When a train leaves a segment belonging to a block it checks out of the block. This has the effect of setting the track-circuit boolean to false. As a check on system functioning, we make sure that the train-id of the train is the same as that of the train occupying the block. This is not a check that can be done in the real hardware, since trains are dumb. Moreover, the section should be in the unblocked state */ void check_out(Train t) {track_circuit = false; if (state != unblocked) Main.error("train " + t + "checked out of a block unexpectedly"); if (t.ident != train.ident) Main.error("train " + t + "checked out of a block which should have contained train "+ train); } /* -- 6.2.3 bk.accept(trainspec) accepts a train into a block ----------- */ void accept(TrainSpec t) {t.previous.accepted = true;} /* -- 6.2.4 bk.request(train,bk_next) requests access to a block --------- Here train is a train-specification referring to the train in the current block. We make a new train-spec for the next block, bk_next, which refers to this block, and put the train-spec in the queue for that block. ??? note we need to lock the queue. */ void request(TrainSpec train, TrackBlock bk_next) {int tid = train.ident; bk_next.q.put(new TrainSpec(tid,this));} /* -- 6.3.1 The Free State ----------------------------------------------------- A track section enters the free state when the track-circuit detector undergoes a transition from the Busy to the Clear state. It may also be in this state following system initialisation (indeed this is the default, overridden by a Placement message). In the Free state the main section process monitors the Request queue. The process remains in the Free state until the RequestQueue is found to be non-empty. If the RequestQueue is non-empty, a Request is chosen from among those queued. This request contains a train-identifier which is used to provide parameters required by subsequent states. The Request is cleared from the queue, but the originator of the Request is not immediately notified of acceptance. The route through the block for the new train extracted. */ void run_free() { System.out.println("run_free: " + this); accepted = false; while (q.empty()) {}; // wait until train wants in train = (TrainSpec)q.next(); // next train for block route = routing[train.ident]; // get its route state = preparing; // and we'll prepare for it } /* -- 6.3.2 The Preparing State ------------------------------------------------ In this state the track-switches are set by the main section process. It takes typically 100ms to set a given switch. They are set sequentially. How long it takes to set a switch will depend on the depletion of the 10,000microfarad capacitor on the power-supply for the track-switches. Once all the track-switches have been set, the MSP enters the Expecting state. */ void run_preparing() { int n_sw = route.junction.length; System.out.println("run_preparing: " + this); for (int i=0;i