Train Operating System
Description of Project:
My semester capstone project was to design and implement an operating system for the Computer Controlled Railroad (CCR) that reacts to external events. The general requirements included:
Inherited Code and ideas:
My involvement with the CCR began in the spring semester of 2000. Laura Weiland and I brainstormed many different approaches to beginning a train operating system. The code we inherited had its strong points. The previous group who had worked on the train designed a program using Visual Basic 6.0 to run the trains around the track. A user could control the trains and the turnouts through the computer and view a visual representation of the track that highlighted photocells as a train passed. Mark Sternig and Dr. Pankratz wrote one of the major contributions of this group. Together they wrote a library file (dp.lib) and a dynamically linked library file (ccr.dll) which was able to open the peek and poke ports and read the status of the turnouts and photocells. These base functions also provided means to toggle a turnout and finally to close the ports. These files used in my code. Note I include ccr.h and dp.lib to call functions implemented in ccr.dll.
Although this previous semester did get the trains to move around the track after viewing the code, it was recognized that it had major flaws and design issues. The Visual Basic Code used an Access Database to keep track of a train’s direction, speed, and id number. Thus, for each call to change the characteristics of a train calls to a database needed to be made. This process could be slow and given the size of the track, it did not need to be in a database. Furthermore, the group used many global variables to keep track of its data. For example, they used a global variable to keep track of a queue’s head pointer. This left open many issues of data security.
The original idea for an operating system allocated both photocells and turnouts. Arrays were used to keep track whether covering a photocell going a certain direction was either an arrival or departure. Given this, the algorithm could keep track of which turnouts were "locked" and which the user could still toggle. Given the direction of the train around the track, arrays were used to look up what section of track and turnout needed to be allocated or unallocated. Trains that were allocated resources were kept track of in two arrays. One array was filled with zeros or the train’s id that owned that section of track. The other array kept track of turnouts and similarly was number zero if the resource was not allocated or numbered the id of the train that owned it. The major differences between this algorithm and the one designed this year lie in two areas. First, this past algorithm physically numbered new sections of track. These sections of track usually lied between two photocells, but also extended beyond that to into critical parts of the track were turnouts were located. Photocell six never going to be used to allocate resources, but rather to change the toggle the train’s direction around the track. I began to implement this design in Visual Basic code. In programming this I found too many exceptions. The design did not necessarily match the actual hardware of the track. However, many data structures from this design would carry over to the new design. The main difference was the difference was the transition from allocating sections of track to photocells. This created the idea for the TrackList data type and simplified the algorithms for updating resources. I decided to discard the original design and code about eight weeks into the semester and start with a new design and to write in C++.
The New Design:
Originally, C++ was used not only because of my familiarity with the language, but also because I had planned on using more inheritance between classes than the end product illustrates. I had envisioned writing one TrainList class and inheriting this object into two other classes known as ActiveTrainList and BlockedTrainList. This original idea was implemented for a short period, but was discarded when both became member variables of the ResourceManager class. One of the benefits that was discovered later in the semester was that creating a dynamically linked library from C++ was more portable than creating the same dynamically linked library in Visual Basic.
After deciding to discard my old design and code, I discussed different methods with each of the CS faculty. Professor Blahnik instructed me to create new id numbers for each resource on the track. Both turnouts and photocells were to be considered resources. These next resource for a train would be found through the formation of a graph. However, my understanding of this design was that a train would own only one resource at a time. This design did not seem practical to me because I could not calculate when a train was over a turnout. There is no feedback to the computer except through turnouts, and I did not want to construct an operating system to required me to calculate the position of a train given its speed and direction. This type of logic would be both difficult and unreliable given that outside forces like a dirty track could slow down or derail a train.
After thinking about this logic for some time, I found a solution that might have worked. A graph should indeed be used to represent a track. The graph would be ideal to represent the track because it could provide a path from photocell to photocell. During this time, I was drawing various tracks to see if the design would work. The major problems occurred when two turnouts lied together on a track with no photocell between them or the track changed direction by traveling along a path. Neither of these cases were present in the current system, but I felt that they were important to consider. Two lessons were learned from this thought. First, to implement a system with two consecutive turnouts with no photocell between creates a problem. Both turnouts need to be locked and turned in a given way. The current system does not allow for this to occur since the TrackList data type assumes that each photocell reaches only one turnout. One possible solution to this problem for future reference is combine this solution with the Professor Blahnik’s recommended solution. A full graph of the track needs to be designed and the function keeps reading resources from the graph until another photocell is found. That is, if I hit photocell zero I would need to look up in the graph function which resources were next. If turnout one was connected to photocell zero, I would allocate that resource if possible and read the next resource in the graph. The cycle continues until the next photocell could be allocated, or a resource (turnout or photocell) that was requested could not be allocated. In this case the train would be forced to stop and be put on the blocked list. The second lesson learned from this designed was that a track that the changes direction for a train would create many difficulties. When a situation like this occurs an actual short is created on the track and the trains outside wheel becomes its inside wheel. The graph for this situation would need to include data of whether or not a train would need to change its direction around the track. Also since the change occurs over a turnout, a "fake" photocell may be needed to indicate when a train reaches this critical point in the track.
In constructing this design I still did not see how trains could avoid collision. Allocating only one resource to a train at a given point would not avoid collision because if two trains would be traveling in opposite directions around the track and come to the same photocell at roughly the same time there may not have been enough time to stop both trains before a collision. During a discussion with Dr. Pankratz, he convinced me on the design of a train owning two photocells, although he originally explained the design in terms of the train owning a section of track that contained one photocell where there was an invisible boundary that the trains could not pass. During this discussion we discovered how trains are separated if they own two photocells and how these two photocells must be swapped when a train changes direction. This design still worked well with the idea of the graph to provide a function to find the next resource. I implemented a crude simulation of this system shortly after this discussion. This basic implementation can be found on my website.
The Two photocell method:
The two photocell design works because it ensures that there is always at least one section of track (the area between two photocells) between the trains. During this design a train owns both the photocell that it already passed (photocellhas) and the photocell that it will cover next given that it continues in the same direction around the track (photocellnext). In this way a train are stopped when it arrive at a photocell and it cannot be allocated the next photocell that it will need to continue travel. In this case the train would not be able to be allocated this resource because some other train needs this resource as its next photocell. This is what creates the space between trains on the track given the two photocell method.
Beginning the Current Implementation:
With the basic top down design implemented in a the simple simulator, I now needed to start to separate code into various source and header files. I wanted this code to be separated to limit compile time and to provide a way for future students to abstract from my data structures without including an entire file. I continued with the idea of a simulated where key strokes represented events of photocells, but began to separate my code into various classes.
I began implementation using a bottom up design. With this idea, I designed the train class first. I constructed a train class to have private data members and functions to control speed, direction, and lights. Since all data members were private, I also wrote many assessor functions to return the private variables. Next, I wrote a TrainList class that was a linked list of trains. I also wrote an ActiveTrainList and a BlockedTrainList class that inherited the TrainList class an overrode the virtual add and delete function of the TrainList class.
I thought that I needed to write these two separate train lists because the active list would need to update the photocell and turnout manager arrays in the resource manager. Therefore, the ActiveTrainList, I thought, needed to be a friend of the ResourceManager class in order to have access to its private data structures. The necessity for two different classes of train lists disappeared when they both became members of the resource manager. This paper will discuss later in the paper.
At this point, I continued to write the ResourceManager class and the TrackList class. The ResourceManager class contains data structures called the photocell manager and turnout manager. These two data arrays of integers keep track of which trains own specific photocells and turnouts. These arrays are of MAXTURNOUTS, AND MAXPHOTOCELLS size, which on the current track are sixteen and six respectively. (Note that there are only five physical turnouts, but I treat photocell six as a logical turnout. This will be addressed later in this paper.) The TrackList class is actually a linked list. A single instance of a TrackList class is of no importance to the track since a it only tells what resources are related to a single photocell. The TrackList data type becomes a useful graph of the actual track layout when I make an array of TrackList classes. When I made two arrays of size MAXPHOTOCELLS of TrackLists, I could describe the entire track in a graph. One of the arrays of TrackLists was called CC while the other array of TrackLists was called CW. These array structures give the relationships of photocells and turnouts in a counterclockwise and clockwise train direction respectively.
These classes were tested, individually at first, to ensure that they worked as implemented. Finally, I put together a main program in console mode to test the classes that I had designed. This main program acted as a simulator for the operating system and can be referenced on my web site See Simulator Version 2. These classes make up the kernel of the operating system. This simulator was event driven, but rather than receiving events from photocells, it received events from the keyboard signifying a photocell being covered.
With some minor errors this project worked well. Through experimentation with this system I discovered that photocell six could not be used as I intended. Since I did not want to switch the direction of the train at photocell six the function described by the array of TrackList ceased to be a function. (Note that because we wanted to save switching the direction of a train for the situation an actual short is created in the track and a train’s outside wheel becomes its inside wheel). Going clockwise around the track hitting photocell six wanted two different photocells, five or twelve. I solved this problem by ignoring photocell six and allocating the sections of track between photocells eleven and five and also four and twelve respectively. During this experimentation, I also discovered that I was not checking for a nonexisted turnout in the allocation of the resources in the ResourceManager. I discovered this when the allocation for photocell fifteen would be updated to zero without the train that owned that photocell moving. This occurred because I was putting a zero in the turnout manager in the –1 position and overwriting memory in the photocell manager. Fixing these two minor errors took some tracing of code, but were relatively easy to fix.
During this time I also had some trouble including certain header files. For reasons that Dr. McVey nor I can explain we were having trouble including ActiveTrainList class into the ResourceManager class. We would get odd compilation errors that informed us that ActiveTrainList was undefined and that we could not declare a zero byte array for the photocell manager. I believe that somewhere there may be wrong includes, but neither Dr. McVey nor I could find the error. I was also concerned because I was unsure where to define the train lists and the tracklists. I did not want the user interface to have to declare these variables an send them to each of the managers. This problem was solved when they were all include as members of the resource manager.
Next, I decided that it was time to move the simulator over to the actual train system. I began to write a photocell manager class that would keep track of when photocells were covered and then uncovered. At first I wanted this class to catch the event and display a message on the screen stating an arrival or a departure. However, I ran into problems during this stage, because there was a misunderstanding of which library to include to use the ccr.dll function. Both Dr. Pankratz and I thought that we needed to include the ccr.lib and ccr.dll to use the turnout and photocell functions. However the ccr.lib was out of date. The actual library that needed to be included was dp.lib. This library pointed to the Get_PC_Status() function located in the ccr.dll that was needed to poll the status of a photocell. (The function returns 1 if the photocell is covered and 0 if the photocell is not covered.) The ccr.lib did not point to this function so the linker gave an error that the function was not defined.
When we found the correct library the photocell manager could be written. We originally had two minor problems. First, the photocells were set at the wrong sensitivities and this caused the photocell to turn on and off automatically. This created a problem with checking for arrivals and departures because the computer would show an arrival or departure when there was no event. The second problem was that I was updating one photocell at a time in a loop. The algorithm to handle an arrival and a departure needed two arrays. One that kept track of the current state of the photocells and one that kept track of the previous state of the photocells. When these two arrays differ we need to handle an arrival or a departure. If the previous state of a photocell was uncovered and covered in the current state we needed to handle an arrival. However, if the previous state showed that the photocell was covered and the current state showed uncovered we needed to handle a departure. To be surer that the code would update the current state of all photocells, I wrote a function that read in the current state of all photocells into the CurrentPCStatus array inside the photocell manager.
During the writing of the PhotocellHandler class I realized that that many of the classes written should be declared inside of the ResourceManager. The TrackList and both TrainLists became data members of the ResourceManager. Also the PhotocellManager class was no longer needed since the resource manager was able to capture the timer events and poll the photocells. The kernel of the operating system lied wholly in the ResourceManager. It made sense that all of these data structures were part of this class and made the coding much simpler. ActiveTrainList and BlockedTrainList classes disappeared and both the ActiveTrainList and BlockedTrainList variables were declared as TrainLists. This could occur because now both members had access to the private variables inside of the ResourceManager class and the ActiveTrainList could directly add to the PhotocellManager array when a train was added to the track because the array photocellmanger was a member of the ResourceManager Class.
Since I was still unable to run any tracks around the track because the dcc.dll was not created yet, I began to test the system by holding my hand over the top of photocells to signify a trains movement. For testing sake, I actually showed the ResourceManager window with the contents of the array photocellmanager and turnoutmng on the screen. By doing this, I was able to watch the trains move around the track by watching the resources that they had be updated. Also at this point, I separated the HandleArrival and HandleDeparture functions. Previously, both of these functions were part of the same function and was handled on the arrival of the photocell. I broke this up do to modularity, however, in the future one may find that it is actually easier and more efficient to handle the arrival and departure at the same time.
Most of major problems I encountered while debugging at this time were errors that took a long time to find, but were relatively easy to correct. First, in the HandleArrival function, I was attempting to update some of the values a train before checking to see if the train was NULL. Whenever a photocell was covered that did not have a train owning it the system would crash because I was pointing with NULL. I simply moved the statement to check if the train was NULL up before checking other data. Secondly, I had a small error in the ActiveToBlocked and BlockedToActive functions. When writing these functions I had forgot to return after moving the train. This created an infinite loop every time a train was blocked. The same error was in both modules. Furthermore, I had a problem traveling through the inner figure eight. I realized after some time that I had set up the TrackList function to use a "fake" turnout six where photocell six is located. I had given the TrackList instructions that would give the next photocell given that this turnout was straight or curved. Since there was no turnout, I figured that this would return either one of these outputs. However, if one polls a turnout that does not exist, the GetTOStatus function will return zero as if the turnout is moving. If one looks at the Load function in the ResourceManager one will notice that turnout six is always associated with a moving turnout. This allowed the function to work.
With the correction of these errors, the system was nearly complete. Next, I needed to find a way to actually move a train from the BlockedList back to the ActiveList. The original plan was to keep track of both the blocking photocell and/or the blocking turnout. This idea was much more complicated than I originally hoped because a turnout alone can not wake up a train. I decided to only keep track of the photocell that the train could not be allocated that blocked it. To do this, I also changed my original plan to stop the train under INTERACTIVE mode if the turnout was toggled in such a way that travel was impossible. It made it much easier just to toggle the turnout for the user and allocate the resources if possible. In this way, I did not think that a turnout could block a train. Later, I discovered that this was not necessarily true. If we remain with the logic of an invisible turnout six a turnout may block a train to prevent a collision at the center of the figure eight. Right now when this happens the train remains blocked and cannot be moved back to the activelist because it does not know when turnout six is deallocated. We may need to keep track of turnouts, or just this invisible turnout to activate and deactivate trains in the figure eight track.
I wrote a function that was called when resources were deallocated to check if any trains were blocked by the photocell that was returned to the system. This function loops through BlockedList and checks to see if the photocell released if equal to any of the blocked trains Blocking photocell. If it finds a match that train is moved to the active list and I fake an arrival and a departure (unless it is still covering the photocell) and if the train can be allocated the next resources it executes its last speed command and will start moving. If it cannot be allocated its next resources it is blocked again.
DCC Motor Commands
With this function in place, all that was needed to was to actually send the trains motor commands. Dr Pankratz used some code I wrote to open a comm port in C++ to write functions to send the trains DCC commands. We initially had some problems finding a place to define a handle for the comm. port. First, we the compiler did not want to allow us to use the HANDLE identifier because it was part of windows.h and it stated that we could not use the windows header file inside of a MFC project. Dr. Pankratz rewrote code typecasting the HANDLE to a unsigned long. While this cleared up the first error, we were still unable to find a place to define the long int handle. We could define it when we were going to issue a motor command in the train method SetSpeed, but then it was necessary to open and close the port each time the command was sent. This was slow and unnecessary. I wanted to open the comm. port inside the resource manager when I opened the other ports to read the photocell and turnout statuses. However, when I tried to define the handle for the comm. port here I the compiler gave me many redefinition errors. After some time, I decided to wrap Dr. Pankratz’s .dll functions and define a global handle inside of that file. This worked wonderfully, and I recommend that Dr. Pankratz change his functions to include a global handle so that the wrapper functions are no longer needed. Also we could not get the function command to work which makes the headlight and taillight operations incomplete inside of the train methods.
User Screens:
With some minor tuning the operating system was in its final state for this semester. Next, I began to write code for the user interface. Till this point, the CUserScreen class just opened the other windows and had a menu option to toggle turnout one. Rachel and I designed look of user screen. I loaded the bitmap from the last semester on the screen and drew circles on the bitmap to represent Turnouts and Photocells. Clicking on the turnouts dots allows a user to toggle the direction of a turnout All points needed to be relative to the upper left corner to the bitmap so that the system could be implemented on different screen sizes or if someone else wanted to place the bitmap in a different place one the screen. I implemented two list boxes that inform the user about the status of the system by outputting train information about all trains on the course and informing the user if turnouts can be toggled at the moment. I added buttons that allowed the user to disable the entire course and then enable the course again. Finally, I put text on the screen to show which trains owned the photocells around the track.
I also wrote the TrainControl class. This window allows a user to give trains commands. It shows the train’s status, id, resources, speed, headlight status, and taillight status, and provides buttons to brake, change a trains speed, and adjust the headlight or taillight status. Both user windows use timers to display the correct data on the screen. Every time the timer is fired, the data on both the TrainControl class and CUserWindow are updated with current values. The data and the display are separated.
Some problems that I encountered at this point are still unexplained. To draw the photocells and turnouts on the screen, I wrote classes that contained a photocells and turnouts location and color. When I declared these classes inside of CUserScreen the system crashed. If I declared them globally everything executed correctly. Finally, I changed the class to a struct the everything worked well. I also needed to fix the constant flashing that occurred on the CUserScreen because of timers updating the screen. I fixed this by using arrays to keep the current and past statuses of the turnouts and photocells. Only when the arrays differ is the screen updated. This made the screen much easier to look at.
This was the last major update of the system. Rachel and I are trying to get the dialogue boxes to initialize the track running. Right now a user must change the code to set up the track with different trains. This is done in the constructor of CUserScreen. I recommend looking at both my code and power point documents and website for more detailed information on the classes.