hoyden

I will collect documentation in the following posts. One of my early challenges was how to name everything that needed naming; blocks, control points, indications, &c. 

When I first developed my LocoNet listener I captured the desired values in hexadecimal because that was easiest. OTOH JMRI uses an octal or decimal representation, or a system name. I put up with the mismatch because I had bigger fish to fry and I did not have a unified naming scheme that would work both in the signal software and JMRI. Finally, about a month ago, I began to settle on a scheme. For turnouts I use a format SW_x_y where x is a number starting at 0 (just about everything in software starts at 0) that indicates the CP where the switch is located and y designates Main 1, Main 2, branch, or yard. So now, instead of using raw hexadecimal I have the following:

typedef enum
{
SW_0_1 = 0x00, // CP0 M1 West Rapidan
SW_0_2 = 0x08, // CP0 M2 West Rapidan
SW_0_B = 0x10, // CP0 M2 Shortline Hill
SW_1_1 = 0x11, // CP1 M1 East Rapidan
SW_1_2 = 0x1C, // CP1 M2 East Rapidan
SW_1_B = 0x1D, // CP1 M2 Linden Branch
SW_2_1 = 0x01, // CP2 M1 Altavista
SW_2_2 = 0x09, // CP2 M2 Altavista
SW_2_Y = 0x15, // CP2 M1 Yard Linden East
SW_3_1 = 0x0A, // CP3 M1 North Linden
SW_3_2 = 0x02, // CP3 M2 North Linden
SW_3_Y = 0x12, // CP3 M2 Yard Linden North
SW_4_1 = 0x03, // CP4 M1 Afton
SW_4_2 = 0x0B, // CP4 M2 Afton
SW_4_B = 0x13, // CP4 M1 Branch
SW_5_1 = 0x05, // CP5 M1 Sheldon Jct
SW_5_2 = 0x0D, // CP5 M2 Sheldon Jct
SW_5_3 = 0x0E, // CP5 M2 Main 3
SW_5_B = 0x16, // CP5 M3 Shortline Hill
SW_5_Y = 0x14, // CP5 M2 Yard Linden South
SW_6_3 = 0x18, // CP6 M1 M1 East Linden XOE
SW_6_4 = 0x19, // CP6 M2 M2 East Linden XOE
SW_6_5 = 0x20, // CP6 M1 M1 East Linden XOW
SW_6_6 = 0x21, // CP6 M2 M2 East Linden XOW
SW_6_Y = 0x07, // CP6 M1 Yard Linden West
SW_7_1 = 0X1A, // CP7 M1 West Linden
SW_7_2 = 0X0F, // CP7 M2 West Linden
SW_7_3 = 0X1B, // CP7 M3 Main 3
SW_7_B = 0X17, // CP7 M3 Linden Branch
// Yard East 1
// Yard East 2
// Yard East 3
// Yard East 4
// Yard East 5
// Yard East 6
SPA__5 = 0x04, // Spare LT5
}SWITCH_NAME;

It's not ideal but it's an improvement. There are 64 turnouts so the list is incomplete. The various yard switches still need to be named and this scheme might not be suitable.  I'm still working on a suitable representation for 64 blocks.

Nancy

Reply 0
greg ciurpita gregc

github

as a professional embedded software developer, i strongly suggest you document your code using github.   while we didn't use github which is a public, we did use  git and gerrit for source code control and review at qualcomm.

github supports version control, reviews and can include other files supporting the project.   that main page for a repository, and there can be many separate project repositories can include html like page (.md (?)) to provide an overview and documentation.  (see Interlock)

it looks like personal messaging is no longer support (at least the way it was) on the new forum

greg - LaVale, MD     --   MRH Blogs --  Rocky Hill Website  -- Google Site

Reply 0
hoyden

GitHub and documentation

Greg, this is a timely discussion. I am exploring setting up a GitHub repository. I am also exploring using Hugo to create documentation. I am at the bottom of the learning curve on both items. I use SVN for my repository and that has been adequate for my local efforts. I have used git and doxygen in my day job (developing real time embedded software). I did not like doxygen so exploring Hugo. I believe Hugo is supported on GitHub but not clear how to use it. I would like to start using some tool to assist with documentation. If the documentation can be embedded in the source, ala doxygen, then that's a win. I would like to avoid maintaining code and documentation separately. I appreciate your feedback and suggestions.

Nancy

Reply 0
greg ciurpita gregc

! oxygen

i'm not reluctant to write and believe i'm better than most (but cringe when a reread stuff from a while back).   i looked at oxygen years ago and decided it wasn't adequate.    details can be left out of documentation since they're in the code.   i try to provide a map of where to look for things.   having come from bell labs and using troff, it was easy to switch to html and now .md.

don't know how closely you looked at my Interlock project, but it's data driven which allows non-programmers to update tables describing routes.

"Show me your flowchart and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowchart; it'll be obvious." -- Fred Brooks, The Mythical Man Month (1975)

i can help you with github.   there is a desktop app but i use cygwin and can run git commands directly in bash

contact me greg.ciurpita@gmail.com  

greg - LaVale, MD     --   MRH Blogs --  Rocky Hill Website  -- Google Site

Reply 0
hoyden

Working out the details

Hi greg, Thank you for your contact. I will plan to reach out. I have a GitHub account. Next I need to explore how best to use it. I chose GNU General Public License v3.0 because it seems like it covers my intent in going public with my code. I'm not trying to market the code. I am sharing it as an example of a simple whole layout signaling system. I am curious how applicable the design would work on other layouts.

My system is also table driven and supplemented with logic. Looking at the font options for a post it appears I can create Courier text that preserves indentation.

Nancy

Here is a test of the formatted font option. This enumeration defines the bit patterns assigned to light individual LEDs on a 2-head signal. If this works then I will post other tables.

typedef enum
{
HI_RED = 0x01,
HI_YELLOW = 0x02,
HI_GREEN = 0x04,
LO_RED = 0x08,
LO_YELLOW = 0x10,
LO_GREEN = 0x20,
E_STOP = HI_RED
E_RESTRICTING = HI_RED
E_SLOW_CLEAR = HI_YELLOW
E_APP_SLOW = HI_YELLOW
E_MEDIUM_APP = HI_RED
E_APPROACH = HI_YELLOW
E_APP_MEDIUM = HI_YELLOW
E_MED_CLEAR = HI_RED
E_APP_LIMITED = HI_YELLOW
E_LIMITED_CLR = HI_RED
E_CLEAR = HI_GREEN
E_DARK_DARK = 0x00, // Dark / Dark E_____SPARE = 0x3F // All on / All on }E_ASPECT;
 
Reply 0
hoyden

System Table

Per greg's comment, a table can do more to explain operation than a flowchart. Both are useful. Here is the System Table that is the heart of CP processing. Every physical CP is represented here. 

The table contains the following information for each physical CP

1. The home block (OS) that denotes track occupancy in the CP

2. Next logical CP east and west (as opposed to the next physical CP)

3. Next block east and west. The "nnb*" entry in bl_next indicates that there is no block.

4. The turnouts that govern the CP. They are defined by their hexadecimal LocoNet value. I plan to replace the value with a symbolic name.

The CP processing logic uses this table to generate an initial indication for a CP.  

const S_CTL_PT1 cp_map1[ E_CP_SIZE * E_MAIN_SIZE ] =
{ // block home cp east cp west east west
{ .cp_home=M1_W_RAPIDAN, .cp_next={ M1E_ALTVISTA, M1W_WEST_LINDEN }, .bl_next={ nnb1, _1M1 }, ._switch={0x00, 0x08, 0x10} }, // 0 M1_WEST_RAPIDAN
{ .cp_home=M2_W_RAPIDAN, .cp_next={ M2E_ALTVISTA, M2W_WEST_LINDEN }, .bl_next={ nnb2, _2M1 }, ._switch={0x08, 0x00, 0x10} }, // 1 M2_WEST_RAPIDAN
{ .cp_home=M1_W_RAPIDAN, .cp_next={ M1E_ALTVISTA, M1W_WEST_LINDEN }, .bl_next={ _1M3, nnb1 }, ._switch={0x11, 0x1C, 0x1D} }, // 2 M1_EAST_RAPIDAN
{ .cp_home=M2_E_RAPIDAN, .cp_next={ M2E_ALTVISTA, M2W_WEST_LINDEN }, .bl_next={ _2M2, nnb2 }, ._switch={0x1C, 0x11, 0x1D} }, // 3 M2_EAST_RAPIDAN
{ .cp_home=M1_ALTVISTA, .cp_next={ M1E_NORTH_LINDEN, M1W_EAST_RAPIDAN }, .bl_next={ _1M4, _1M3 }, ._switch={0x01, 0x09, 0x15} }, // 4 M1_ALTVISTA
{ .cp_home=M2_ALTAVISTA, .cp_next={ M2E_NORTH_LINDEN, M2W_EAST_RAPIDAN }, .bl_next={ _2M4, _2M2 }, ._switch={0x01, 0x09, 0x15} }, // 5 M2_ALTVISTA
{ .cp_home=M1_N_LINDEN, .cp_next={ M1E_AFTON, M1W_ALTVISTA }, .bl_next={ _1M5, _1M4 }, ._switch={0x02, 0x0A, 0x12} }, // 6 M1_NORTH_LINDEN
{ .cp_home=M2_N_LINDEN_A, .cp_next={ M2E_AFTON, M2W_ALTVISTA }, .bl_next={ _2M5, _2M4 }, ._switch={0x02, 0x0A, 0x12} }, // 7 M2_NORTH_LINDEN
{ .cp_home=M1_AFTON, .cp_next={ M1E_SHELDON_JCT, M1W_NORTH_LINDEN }, .bl_next={ _1M6, _1M5 }, ._switch={0x03, 0x0B, 0x13} }, // 8 M1_AFTON
{ .cp_home=M2_AFTON, .cp_next={ M2E_SHELDON_JCT, M2W_NORTH_LINDEN }, .bl_next={ _2M6, _2M5 }, ._switch={0x03, 0x0B, 0x13} }, // 9 M2_AFTON
{ .cp_home=M1_SHELDON_JCT, .cp_next={ M1E_EAST_LINDEN, M1W_AFTON }, .bl_next={ _1M7, _1M6 }, ._switch={0x05, 0x0D, 0x06} }, // 10 M1_SHELDON_JCT
{ .cp_home=M3_SHELDON_JCT, .cp_next={ M2E_EAST_LINDEN, M2W_AFTON }, .bl_next={ _2M7, _2M6 }, ._switch={0x05, 0x0D, 0x0E} }, // 11 M2_SHELDON_JCT
{ .cp_home=M1_LINDEN_XO, .cp_next={ M1E_WEST_RAPIDAN, M1W_SHELDON_JCT }, .bl_next={ nnb3, _1M7 }, ._switch={0x18, 0x21, 0x07} }, // 12 M1_EAST_LINDEN
{ .cp_home=M2_LINDEN_XO, .cp_next={ M2E_WEST_RAPIDAN, M2W_SHELDON_JCT }, .bl_next={ nnb4, _2M7 }, ._switch={0x19, 0x20, 0x07} }, // 13 M2_EAST_LINDEN
{ .cp_home=M1_W_LINDEN, .cp_next={ M1E_WEST_RAPIDAN, M1W_SHELDON_JCT }, .bl_next={ _1M1, nnb3 }, ._switch={0x0F, 0x1A, 0x07} }, // 14 M1_WEST_LINDEN
{ .cp_home=M2_W_LINDEN, .cp_next={ M2E_WEST_RAPIDAN, M2W_SHELDON_JCT }, .bl_next={ _2M1, nnb4 }, ._switch={0x0F, 0x1A, 0x1B} }, // 15 M2_WEST_LINDEN
{ .cp_home=BL_UNSPECIFIED, .cp_next={ M2W_AFTON, M2W_WEST_LINDEN }, .bl_next={ _2M6, _2M1 }, ._switch={0x10, 0x16, 0x0E} }, // 16 SHORTLINE_HILL
{ .cp_home=BL_UNSPECIFIED, .cp_next={ M2E_WEST_RAPIDAN, M2E_ALTVISTA }, .bl_next={ _2M1, _2M2 }, ._switch={0x1D, 0x17, 0x1B} }, // 17 LINDEN BRANCH
{ .cp_home=BL_UNSPECIFIED, .cp_next={ M2E_WEST_RAPIDAN, M2W_AFTON }, .bl_next={ _2M1, _2M6 }, ._switch={0x0E, 0x1B, 0x16} } // 19 MAIN_3 sw 0x17
};
Reply 0
hoyden

Route Table

For each physical CP, and for each direction, and for each route through the CP, there is an entry in this table to define the maximum speed through the CP. The .state entry corresponds to the _switch turnouts in the System Table. Turnouts can be normal, reverse, or ignore (the turnout state does not affect the route).

const S_CP_SWITCH cp_main[ E_CP_SIZE * E_MAIN_SIZE * E_DIR_SIZE * CP_SWITCH_SIZE ] =
{ // WEST_RAPIDAN CP_WEST_RAPIDAN Heads
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M1 EAST NORMAL 0 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = LIMITED_CLR }, // M1 EAST REVERSE
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = MED_CLEAR_B }, // M1 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M1 WEST NORMAL 1 0
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M1 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M1 WEST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 EAST NORMAL 2 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M2 EAST REVERSE
{ .state = { SW_NORMAL, SW_NORMAL, SW_REVERSE }, .aspect = MED_CLEAR_B }, // M2 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 WEST NORMAL 3 0
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = LIMITED_CLR }, // M2 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_REVERSE }, .aspect = STOP }, // M2 WEST BRANCH
// EAST_RAPIDAN CP_EAST_RAPIDAN
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M1 EAST NORMAL 4 0
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M1 EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M1 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M1 WEST NORMAL 5 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = LIMITED_CLR }, // M1 WEST REVERSE
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = MED_CLEAR_B }, // M1 WEST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 EAST NORMAL 6 0
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = LIMITED_CLR }, // M2 EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M2 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 WEST NORMAL 7 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M2 WEST REVERSE
{ .state = { SW_NORMAL, SW_NORMAL, SW_REVERSE }, .aspect = MED_CLEAR_B }, // M2 WEST BRANCH
// ALTVISTA CP_ALTAVISTA
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M1 EAST NORMAL 8 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = LIMITED_CLR }, // M1 EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M1 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M1 WEST NORMAL 9 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M1 WEST REVERSE
{ .state = { SW_NORMAL, SW_NORMAL, SW_REVERSE }, .aspect = SLOW_CLEAR }, // M1 WEST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M2 EAST NORMAL 10 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M2 EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M2 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M2 WEST NORMAL 11 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = LIMITED_CLR }, // M2 WEST REVERSE
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = MED_CLEAR }, // M2 WEST BRANCH
// NORTH_LINDEN CP_NORTH_LINDEN
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M1 EAST NORMAL 12 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = MED_CLEAR }, // M1 EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M1 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M1 WEST NORMAL 13 1
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M1 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M1 WEST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 EAST NORMAL 14 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = STOP }, // M2 EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_REVERSE }, .aspect = SLOW_CLEAR }, // M2 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 WEST NORMAL 15 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = MED_CLEAR }, // M2 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_REVERSE }, .aspect = STOP }, // M2 WEST BRANCH
// AFTON CP_AFTON
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M1 EAST NORMAL 16 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M1 EAST REVERSE
{ .state = { SW_NORMAL, SW_NORMAL, SW_REVERSE }, .aspect = SLOW_CLEAR }, // M1 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M1 WEST NORMAL 17 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = LIMITED_CLR }, // M1 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_REVERSE }, .aspect = STOP }, // M1 WEST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M2 EAST NORMAL 18 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = LIMITED_CLR }, // M2 EAST REVERSE
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = SLOW_CLEAR }, // M2 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M2 WEST NORMAL 19 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M2 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M2 WEST BRANCH
// SHELDON_JCT CP_SHELDON_JCT
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M1 EAST NORMAL 20 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = MED_CLEAR }, // M1 EAST REVERSE
{ .state = { SW_NORMAL, SW_NORMAL, SW_REVERSE }, .aspect = STOP }, // M1 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M1 WEST NORMAL 21 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M1 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_REVERSE }, .aspect = RESTRICTING }, // M1 WEST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 EAST NORMAL 22 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = STOP }, // M2 EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_REVERSE }, .aspect = MED_CLEAR_B }, // M2 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 WEST NORMAL 23 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = MED_CLEAR }, // M2 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M2 WEST BRANCH
// EAST_LINDEN CP_EAST_LINDEN
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M1 EAST NORMAL 24 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = MED_CLEAR }, // M1 EAST REVERSE
{ .state = { SW_NORMAL, SW_NORMAL, SW_REVERSE }, .aspect = SLOW_CLEAR }, // M1 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M1 WEST NORMAL 25 0
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = MED_CLEAR }, // M1 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M1 WEST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M2 EAST NORMAL 26 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = MED_CLEAR }, // M2 EAST REVERSE
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = SLOW_CLEAR }, // M2 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M2 WEST NORMAL 27 0
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = MED_CLEAR }, // M2 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M2 WEST BRANCH
// WEST_LINDEN CP_WEST_LINDEN
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M1 EAST NORMAL 28 0
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M1 EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M1 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_IGNORE }, .aspect = CLEAR }, // M1 WEST NORMAL 29 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = MED_CLEAR }, // M1 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M1 WEST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 EAST NORMAL 30 0
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = MED_CLEAR }, // M2 EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M2 EAST BRANCH
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M2 WEST NORMAL 31 2
{ .state = { SW_REVERSE, SW_NORMAL, SW_IGNORE }, .aspect = STOP }, // M2 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_REVERSE }, .aspect = MED_CLEAR_B }, // M2 WEST BRANCH
// SHORTLINE_HILL SHORTLINE_HILL 32
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = MED_CLEAR }, // EAST NORMAL
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // EAST BRANCH
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = MED_CLEAR }, // WEST NORMAL 33
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // WEST BRANCH
// LINDEN_BRANCH LINDEN_BRANCH 34
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = MED_CLEAR }, // EAST NORMAL
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // EAST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // EAST BRANCH
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = MED_CLEAR }, // WEST NORMAL 35
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // WEST BRANCH
// MAIN_3
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = MED_CLEAR }, // EAST NORMAL
{ .state = { SW_REVERSE, SW_NORMAL, SW_NORMAL }, .aspect = STOP }, // EAST REVERSE
{ .state = { SW_REVERSE, SW_IGNORE, SW_REVERSE }, .aspect = MED_CLEAR_B }, // EAST BRANCH
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = MED_CLEAR }, // WEST NORMAL 35
{ .state = { SW_NORMAL, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // WEST REVERSE
{ .state = { SW_IGNORE, SW_REVERSE, SW_IGNORE }, .aspect = MED_CLEAR_B }, // WEST BRANCH
};
Reply 0
hoyden

Route Logic

Here is the routine that determines the route through a CP; normal, reverse, or branch. The routine reads the turnout states for the turnouts listed in the System Table. Then the routine compares the CP switch states with the valid states for the CP in the Route Table. When a match is found then the speed through the CP is stored in the Route Status Table. The routine also checks the CP OS (home) block for occupancy in the block_state table. If occupied then the Route Status is set to Stop. 

E_CP_ROUTE GetRoute(E_CP_NAME cp)
{
uint8_t xx, zz;
E_CP_ROUTE route;
E_SWITCH_STATE state[CP_SWITCH_SIZE] = { SW_UNKNOWN, SW_UNKNOWN, SW_UNKNOWN };
const S_CP_SWITCH *statePtr = &cp_main[ cp * CP_ROUTE_SIZE ];
const S_CTL_PT1 *cpPtr = &cp_map1[ cp 1 ];
__asm("nop");
// Compute the CP state
for( xx = 0; xx < CP_SWITCH_SIZE; xx++ )
{
state[ xx ] = switch_state[ cpPtr-> _switch[ xx ] ];
}
for( route = E_NORMAL; route < = E_BRANCH; route++ )
{
for( zz = 0; zz < CP_SWITCH_SIZE; zz++ )
{
if( statePtr-> state[ zz ] == SW_IGNORE )
{
continue;
}
else if( state[ zz ] != statePtr-> state[ zz ] )
{
break;
}
}
if( zz == CP_SWITCH_SIZE )
{
break;
}
statePtr++;
}
if( zz == CP_SWITCH_SIZE )
{
E_INDICATION retSts = statePtr-> aspect;
E_BLOCK_NAME block = cpPtr-> cp_home;
if( block != BL_UNSPECIFIED )
{
if( block_state[ block ] != E_BL_CLEAR )
{
retSts = STOP;
}
}
route_status[ cp ] = retSts;
}
return route;
}
Reply 0
hoyden

Compute CP Indication for the Normal Route

The signal software iterates through each physical control point to update the CP indication based on the route status; normal, reverse, or branch. I will break this into three posts to simplify the description. Here is the common entry code and the normal route logic.

  1. E_DIRECTION dir = cp & 0x01; and the next few statements get the direction of traffic and set the initial computed indications to Stop.
  2. if( cp == trap ) provides a simple way to stop execution at a desired CP to single step the code during debug.
  3. E_CP_ROUTE route = GetRoute(cp); retrieves the route through the CP and sets the computed indication to the value from the route status table. The route status will be either the maximum CP speed or Stop if the OS is occupied.
  4. block = cpPtr-> bl_next[dir]; and the next few statements check the next block for occupancy. If occupied then set the CP indication to Stop. We're done processing the normal route.
  5. if(retSts != STOP) If  the CP OS and the next block are not occupied then continue signal processing.
  6. cp_next1 = GetNext( cp, NEXT_CP ); gets the next physical CP.
  7. cp_next2 = cpPtr-> cp_next[ dir ]; gets the next logical CP
  8. sts2 = display_status[ cp_next2 ]; gets the next logical CP displayed indication
  9. sts2 = GetNextAspect(sts2); upgrades the displayed indication for the current CP. For example, if the next CP is Stop then set the current CP indication to Approach.
  10. if( cp_next1 != cp_next2 ) checks if the next physical CP is equal to the next logical CP. If the CPs are not equal then check the next physical CP. This logic checks for one additional physical CP.
  11. sts1 = route_status[ cp_next1 ]; If the logical and physical CPs are different then get the route status for the next CP which is in advance of the current CP.
  12. if( sts1 == LIMITED_CLR|| sts1 == MED_CLEAR )if the next CP route status is Limited Clear or Medium Clear then we know the next CP crosses over to the other Main.
  13. cp_next1 = GetNext( cp_next1, NEXT_TRK ); gets the CP for the other track.
  14. cpPtr = &cp_map1[ cp_next1 1 ]; computes a pointer to the System Table to allow us to check the OS for the CP.
  15. if( block_state[ block ] != E_BL_CLEAR ) and the next few statements check the block in advance of the next CP for occupancy. If occupied set the indication to Stop.
  16. else if the block is not occupied then get the displayed indication for the block in advance.
  17. sts2 = GetNextAspect(sts2); upgrades the displayed indication to the current CP. See step 9 for details
  18. retSts = retSts < sts2 ? retSts : sts2; Choose the more restrictive indication from either the current CP or the next CP.
  19. retSts = GetIndication( cp, retSts ); performs any "Special Case" logic for the CP such as a turnout that is not accounted for in the System Table.
  20. retSts contains the computed indication for the normal route through the current CP.
void Ctl_Pt_Status1( E_CP_NAME cp )
{
E_DIRECTION dir = cp & 0x01;
E_INDICATION sts1 = STOP;
E_INDICATION sts2 = STOP;
const S_CTL_PT1 *cpPtr = &cp_map1[ cp 1 ];
E_CP_NAME cp_next1;
E_CP_NAME cp_next2;
E_BLOCK_NAME block;
if( cp == trap )
{
__asm("nop");
}
E_CP_ROUTE route = GetRoute(cp);
E_INDICATION retSts = route_status[ cp ];
switch(route)
{
case E_NORMAL:
block = cpPtr-> bl_next[dir];
if( block_state[ block ] != E_BL_CLEAR )
{ // Check next block
route_status[ cp ] = retSts = STOP;
}
if(retSts != STOP)
{ // Next block clear. Check next CP
cp_next1 = GetNext( cp, NEXT_CP );
cp_next2 = cpPtr-> cp_next[ dir ];
sts2 = display_status[ cp_next2 ];
sts2 = GetNextAspect(sts2);
if( cp_next1 != cp_next2 )
{ // Check next CP
sts1 = route_status[ cp_next1 ];
if( sts1 == LIMITED_CLR
{ // Check next CP other track cp_next1 = GetNext( cp_next1, NEXT_TRK ); } cpPtr = &cp_map1[ cp_next1 1 ]; block = cpPtr-> bl_next[dir]; if( block_state[ block ] != E_BL_CLEAR ) { // Check next block sts2 = STOP; } else { // Get next CP other track cp_next1 = GetNext( cp_next1, NEXT_CP ); sts2 = display_status[ cp_next1 ]; sts2 = GetNextAspect(sts2); } sts2 = sts2 < sts1 ? sts2 : sts1; } retSts = retSts < sts2 ? retSts : sts2; retSts = GetIndication( cp, retSts ); } break;
Reply 0
hoyden

Compute CP Indication for the Reverse Route

Computing the CP indication for the reverse route uses similar logic to the normal route. The difference is we're being crossed over to the other Main. The following will describe an overview of the steps. The detail at each step is identical to the detail for the normal route.

  1. cp_next1 = GetNext( cp, NEXT_TRK ); gets the CP for the other Main.
  2. cpPtr = &cp_map1[ cp_next1 1 ]; computes a pointer to the System Table for the CP
  3. block = cpPtr-> cp_home; and the next few statements check the OS at the CP.
  4. if( retSts != STOP ) If the OS is not occupied then continue computing the indication.
  5. if( cp_next1 != cp_next2 ) Check if the next physical CP is not equal to the next logical CP.
  6. sts1 = route_status[ cp_next1 ]; Process the intervening physical CP. Determine the indication for the next physical CP. The next physical CP may cross us over back to our original Main. 
  7. else The next physical CP equals the next logical CP. Compute the indication for the next CP at the other Main.
  8. retSts = retSts < sts1 ? retSts : sts1; Choose the more restrictive indication from either the current CP or the next CP.
  9. retSts = GetIndication( cp, retSts ); Performs any "Special Case" logic for the CP such as a turnout that is not accounted for in the System Table.
  10. retSts contains the computed indication for the reverse route through the current CP.

 

      case E_REVERSE:
// Check other track at current location
cp_next1 = GetNext( cp, NEXT_TRK );
cpPtr = &cp_map1[ cp_next1 1 ];
block = cpPtr-> cp_home;
if( block_state[ block ] == E_BL_OCCUPIED )
{
route_status[ cp ] = retSts = STOP;
}
if( retSts != STOP )
{
// Check other track at next CP
cp_next1 = GetNext( cp, NEXT_CP_TRK );
cp_next2 = cpPtr-> cp_next[ dir ];
if( cp_next1 != cp_next2 )
{ // Check next CP
sts1 = route_status[ cp_next1 ];
if( sts1 == LIMITED_CLR
{ // Check next CP other track cpPtr = &cp_map1[ cp_next1 1 ]; block = cpPtr-> bl_next[dir]; if( block_state[ block ] != E_BL_CLEAR ) { // Check next block sts1 = STOP; } else { // Get next CP other track cp_next2 = GetNext( cp_next1, NEXT_CP_TRK ); sts1 = display_status[ cp_next2 ]; sts1 = GetNextAspect(sts1); } } } else { block = cpPtr-> bl_next[ dir ]; if( block_state[ block ] == E_BL_CLEAR ) { sts1 = route_status[ cp_next1 ]; sts2 = display_status[ cp_next2 ]; sts1 = sts1 < sts2 ? sts1 : sts2; sts1 = GetNextAspect(sts1); } else { sts1 = STOP; } } retSts = retSts < sts1 ? retSts : sts1; retSts = GetIndication( cp, retSts ); } break;
Reply 0
hoyden

Compute the CP Indication for the Branch Route

Computing the CP indication for the  branch route is all "Special Processing". In general branch route special processing will check turnouts and occupancy for the arbitrary artifacts that comprise the branch route.

  1. sts1 = GetBranch( cp, retSts ); Compute the indication for the branch route.
  2. retSts = retSts < sts1 ? retSts : sts1; Choose the more restrictive indication from either the current CP or the branch route.
  3. retSts contains the computed indication for the branch route through the current CP.
      case E_BRANCH:
__asm("nop");
sts1 = GetBranch( cp, retSts );
retSts = retSts < sts1 ? retSts : sts1;
break;
Reply 0
hoyden

Compute the CTC Authority

The final step in computing a CP indication is to check the CTC Authority.

  1.  sts1 = GetAuthorityCTC(cp); Gets the authority set by the dispatcher (JMRI Panel). The authority will be either Clear or Stop. If ABS is active then authority will always be Clear.
  2. retSts = retSts < sts1 ? retSts : sts1; Choose the more restrictive indication from either the CP or the CTC authority.
  3. display_status[ cp ] = retSts; saves the CP indication in the display status table.

 

   sts1 = GetAuthorityCTC(cp);
retSts = retSts < sts1 ? retSts : sts1;
display_status[ cp ] = retSts;
Reply 0
hoyden

CP Special Processing

CP Special Processing handles turnouts that are not handled by normal, reverse, or branch CP processing; processing that might be needed after reconciling display indications for logical CPs that encompass two physical CPs; and handling CPs Shortline Hill, Linden Branch, and Main 3. Shortine Hill and Linden Branch are the overlapping reversing loops. I won't go into any detail because this processing is specific to ML&S unless there are any questions.

Special processing computes an indication may be more restrictive than the supplied indication.

 

E_INDICATION GetIndication( E_CP_NAME cp, E_INDICATION sts )
{
E_INDICATION retSts = sts;
E_INDICATION route = route_status[ cp ];
switch( cp )
{
case M1E_EAST_RAPIDAN:
if( switch_state[ 0x15 ] != SW_NORMAL ) // Linden Yard East switch
{
retSts = RESTRICTING;
}
break;
case M1E_ALTVISTA:
retSts = display_status[ M1E_NORTH_LINDEN ];
break;
case M2E_ALTVISTA:
retSts = display_status[ M2E_NORTH_LINDEN ];
break;
case M1W_NORTH_LINDEN:
retSts = display_status[ M1W_ALTVISTA ];
break;
case M2W_NORTH_LINDEN:
// Yard Limit North Linden
if( block_state[ M2_N_LINDEN_A ] == E_BL_OCCUPIED &&
switch_state[SW_3_2] == SW_NORMAL)
{
retSts = STOP;
}
else if(route == CLEAR)
{
retSts = display_status[ M2W_ALTVISTA ];
}
else
{
retSts = display_status[ M1W_ALTVISTA ];
}
break;
case M2E_AFTON:
if(route == CLEAR)
{
if( switch_state[ 0x14 ] != SW_NORMAL )
{
retSts = RESTRICTING; // Linden Yard South
}
}
break;
case M2W_SHELDON_JCT:
if( switch_state[ 0x14 ] != SW_NORMAL )
{
if( block_state[ LINDEN_SOUTH ] != E_BL_CLEAR )
retSts = RESTRICTING;
else
retSts = SLOW_CLEAR;
}
break;
case E_SHORTLINE_HILL:
retSts = (block_state[M3_SHELDON_JCT] == E_BL_OCCUPIED
block_state[_2M6] == E_BL_OCCUPIED ) ? STOP : CLEAR; route = display_status[ M2W_AFTON ]; route = GetNextAspect( route ); if( switch_state[ 0x14 ] != SW_NORMAL ) { // Linden Yard South preempts M2W_AFTON if( block_state[ LINDEN_SOUTH ] != E_BL_CLEAR ) route = RESTRICTING; else route = SLOW_CLEAR; } if( retSts != STOP ) { retSts = retSts < route ? retSts : route; } break; case W_SHORTLINE_HILL: retSts = block_state[M2_W_RAPIDAN] == E_BL_OCCUPIED ? STOP : CLEAR; if( route_status[M1W_WEST_RAPIDAN] == MED_CLEAR_B ) { retSts = ( block_state[M1_W_RAPIDAN] == E_BL_OCCUPIED
block_state[_1M1] == E_BL_OCCUPIED ) ? STOP : retSts; route = display_status[M1W_WEST_LINDEN]; } else { retSts = block_state[_2M1] == E_BL_OCCUPIED ? STOP : retSts; route = display_status[M2W_WEST_LINDEN]; } route = GetNextAspect( route ); if( retSts != STOP ) { retSts = retSts < sts ? retSts : sts; } break; case E_LINDEN_BRANCH: retSts = (block_state[M2_W_LINDEN] == E_BL_OCCUPIED
block_state[_2M1] == E_BL_OCCUPIED ) ? STOP : CLEAR; route = display_status[ M2E_WEST_RAPIDAN ]; route = GetNextAspect( route ); if( retSts != STOP ) { retSts = retSts < route ? retSts : route; } break; case W_LINDEN_BRANCH: retSts = block_state[M2_E_RAPIDAN] == E_BL_OCCUPIED ? STOP : CLEAR; if( route_status[M1W_EAST_RAPIDAN] == MED_CLEAR_B ) { retSts = (block_state[M1E_EAST_RAPIDAN] == E_BL_OCCUPIED
block_state[_1M3] == E_BL_OCCUPIED ) ? STOP : retSts; route = display_status[ M1E_ALTVISTA ]; } else { retSts = block_state[_2M2] == E_BL_OCCUPIED ? STOP : retSts; route = display_status[ M2E_ALTVISTA ]; } route = GetNextAspect( route ); if( retSts != STOP ) { retSts = retSts < route ? retSts : route; } break; case MAIN_3_EAST: if(block_state[ M2_W_LINDEN ] != E_BL_CLEAR ) { retSts = STOP; } break; case MAIN_3_WEST: if(block_state[ M3_SHELDON_JCT ] != E_BL_CLEAR ) { retSts = STOP; } break; default: // No CP fixup processing break; } retSts = retSts < sts ? retSts : sts; return retSts; }
Reply 0
hoyden

Branch Special Processing

Branch Special Processing handles route artifacts (block occupancy and turnouts) that relate to the specific branch. Similar to CP Special Processing, Branch Special Processing may return an indication more restrictive than the supplied indication.

 

static E_INDICATION GetBranch( E_CP_NAME cp, E_INDICATION sts )
{
E_INDICATION retSts = sts;
switch( cp )
{
case M1E_WEST_RAPIDAN:
case M2E_WEST_RAPIDAN:
if( block_state[ SHORTLINE_HILL ] != E_BL_CLEAR )
{
retSts = STOP;
}
else
{
if(GetAuthorityCTC( E_SHORTLINE_HILL ) == CLEAR)
{
retSts = display_status[ E_SHORTLINE_HILL ];
retSts = GetNextAspect( retSts );
}
else
{
retSts = STOP;
}
}
break;
case M1W_EAST_RAPIDAN:
case M2W_EAST_RAPIDAN:
if( block_state[ LINDEN_BRANCH ] != E_BL_CLEAR
block_state[ SHORTLINE_HILL ] != E_BL_CLEAR
block_state[ _2M1 ] != E_BL_CLEAR
block_state[ M2_W_RAPIDAN ] != E_BL_CLEAR
block_state[ M2_W_LINDEN ] != E_BL_CLEAR ) { retSts = STOP; } else { if(GetAuthorityCTC( E_LINDEN_BRANCH )) { retSts = display_status[ E_LINDEN_BRANCH ]; retSts = GetNextAspect( retSts ); } else { retSts = STOP; } } break; case M1W_ALTVISTA: // Linden Yard East case M2W_ALTVISTA: if( block_state[ LINDEN_EAST ] != E_BL_CLEAR ) retSts = RESTRICTING; else retSts = SLOW_CLEAR; break; case M2E_NORTH_LINDEN: // Linden Yard North if( block_state[ LINDEN_NORTH ] != E_BL_CLEAR ) retSts = RESTRICTING; else retSts = SLOW_CLEAR; break; case M2E_AFTON: if( switch_state[0x13] == SW_REVERSE ) retSts = SLOW_CLEAR; // offline siding check for occupancy else if( switch_state[0x14] == SW_REVERSE ) retSts = RESTRICTING; // South Linden reversed break; case M2E_SHELDON_JCT: if( switch_state[ SW_5_B ] == SW_NORMAL ) { // Main 3 East if( block_state[ _3M7 ] != E_BL_CLEAR ) { retSts = STOP; } else { retSts = display_status[ MAIN_3_EAST ]; retSts = GetNextAspect( retSts ); } } // Shortline Hill else if( block_state[ LINDEN_BRANCH ] != E_BL_CLEAR
block_state[ SHORTLINE_HILL ] != E_BL_CLEAR
block_state[ _2M1 ] != E_BL_CLEAR
block_state[ M2_W_RAPIDAN ] != E_BL_CLEAR
block_state[ M2_W_LINDEN ] != E_BL_CLEAR ) { retSts = STOP; } else { retSts = display_status[ W_SHORTLINE_HILL ]; retSts = GetNextAspect( retSts ); } break; case M1W_SHELDON_JCT: // Rathole __asm( "nop" ); break; case M2W_SHELDON_JCT: // Linden Yard South if( route_status[ M2E_SHELDON_JCT ] == MED_CLEAR_B ) { retSts = display_status[ M2W_AFTON ]; retSts = GetNextAspect( retSts ); if( switch_state[ SW_5_Y ] != SW_NORMAL ) { if( block_state[ LINDEN_SOUTH ] != E_BL_CLEAR ) retSts = RESTRICTING; else retSts = SLOW_CLEAR; } } break; case M1E_EAST_LINDEN: // Linden Yard West case M2E_EAST_LINDEN: // Linden Yard West if( block_state[ LINDEN_WEST ] != E_BL_CLEAR ) retSts = RESTRICTING; break; case M1W_EAST_LINDEN: if( switch_state[ 0x07 ] != SW_NORMAL ) retSts = STOP; break; case M2W_EAST_LINDEN: case M1E_WEST_LINDEN: case M1W_WEST_LINDEN: case M2E_WEST_LINDEN: break; case M2W_WEST_LINDEN: if( switch_state[ SW_7_3 ] == SW_REVERSE ) { if( switch_state[ SW_7_B ] == SW_REVERSE && GetAuthorityCTC( W_LINDEN_BRANCH ) == CLEAR ) { // Linden Branch retSts = display_status[ W_LINDEN_BRANCH ]; retSts = GetNextAspect( retSts ); } else if( block_state[ _3M7 ] == E_BL_CLEAR ) { // Main 3 retSts = display_status[ MAIN_3_WEST ]; retSts = GetNextAspect( retSts ); } else { retSts = STOP; } } break; default: retSts = STOP; break; } if( retSts != STOP ) { retSts = retSts < sts ? retSts : sts; } return retSts; }
Reply 0
greg ciurpita gregc

a basic example

i think it would be helpful to discuss a simple basic example: the trackage and what is needed in the code to make it work

greg - LaVale, MD     --   MRH Blogs --  Rocky Hill Website  -- Google Site

Reply 0
hoyden

I agree. I do not have that

I agree. I do not have that information immediately available so I will push that task on the stack. The risk with any documentation is that it doesn't inform the reader.

Nancy

Reply 0
hoyden

Example CP: Afton

I'll use CP Afton to step through the physical plant (tracks and turnouts) and how the signal software computes an indication for each signal.

Here is CP from the track diagram. Main 1 is the lower track and Main 2 is the upper track. Eastbound is left to right. Afton has a crossover and following the crossover a turnout for a branch. 

%20Afton.JPG 

Here is how the track looks in the field facing east. Main 1 is on the right and Main 2 on the left. The signals are located at the west entrance to CP Afton. The track limits for CP Afton are defined by insulated rail joints adjacent to the signals. Track after the signals is part of the CP and track before the signals is a block that extends west to the next CP, North Linden. 

IMG_7233.jpg 

Here is Afton looking west. Main 1 is on the left and Main 2 on the right. The branch is to the left of Main 1. The Main 1 West signal is in the foreground and Main 2 West signal is at the crossover. The CP track limits are defined by insulated rail joints adjacent to the signals. Thus the limits of CP Afton are the tracks between the signals on the east and west. The branch is also defined by insulated rail joints but there is no signal at the joints. The track in front of the signals are the block that extends east to the the next CP, Sheldon Junction.

IMG_7234.jpg 

Here is the dispatcher's view of Afton showing the crossover and branch turnout. Eastbound is left to right. Below the track diagram the two upper levers control the turnouts. Turnout 17, the crossover, is reversed allowing movement between Main 1 and Main 2. Turnout 19 is normal allowing movement on Main 1.. The signal levers are below the turnout levers. Signal 16 governs Main 1 and Signal 18 governs Main 2. Both levers are set to authorize eastbound traffic. The gray button between the signal levers is the Code button. The turnout and signal levers can be set to the desired configuration but the commands are not sent to the CP until the code button is pressed. The green indicators next to the signal levers are lit to indicate that track authority will be restored after a train exits the CP..

Afton.png 

The signal software will compute 5 signal indications: Main 1 east, M2 east, M1 west, M2 west, and Branch west.

To be continued.

 

Reply 0
Jim at BSME

Rotate picture please

Can you rotate the two pictures of the actual track and signals 90 degrees to the right? They probably look fine on a iDevice, but on the computer they are sideways.

- Jim B.
Baltimore Society of Model Engineers, Estd. 1932
O & HO Scale model railroading
Check out BSME on: FacebookInstagram
Reply 0
hoyden

Jim B, that's beyond my paygrade

The pictures looked fine in preview and flipped when I submitted. Grrrr.... I will see what I can do. I pre-rotated it and it didn't help.

Nancy

Reply 0
Jim at BSME

Pictures

Hmm, I don't know if you go to the File Browser under the My Account menu on the upper right and then rotated the pictures there if that would affect the post or not.

Right now the first picture needs to be rotated left (anti/counter clockwise) 90 degrees and the second one right (clockwise) 90 degrees. 

- Jim B.
Baltimore Society of Model Engineers, Estd. 1932
O & HO Scale model railroading
Check out BSME on: FacebookInstagram
Reply 0
hoyden

Be That As It May

CP Afton has the following routes based on the configuration of the turnouts and the direction of traffic. Keep these routes in mind when we see how they are handled in the tables below.

Main 1 East

  • Normal 
  • Branch

Main 2 East

  • Normal
  • Reverse
  • Branch

Main 1 West

  • Normal
  • Reverse

Main 2 West

  • Normal

Here are the two entries in the System Table for CP Afton. Main 1 is the first entry and Main 2 the second. For a given direction, east or west, the CP is occupied if either cp_home or bl_next are occupied. If the CP is occupied then the signal indication will be Stop for that track in that direction.

{ //          block home                 cp east           cp west                         east  west
{ .cp_home=M1_AFTON, .cp_next={ M1E_SHELDON_JCT, M1W_NORTH_LINDEN }, .bl_next={ _1M6, _1M5 }, ._switch={SW_4_1, SW_4_2, SW_4_B} }, // 8 M1_AFTON
{ .cp_home=M2_AFTON, .cp_next={ M2E_SHELDON_JCT, M2W_NORTH_LINDEN }, .bl_next={ _2M6, _2M5 }, ._switch={SW_4_1, SW_4_2, SW_4_B} }, // 9 M2_AFTON

Here are the Route Table entries for CP Afton. The .state values are the turnout states defined in the System Table above in _state. The .state value SW_IGNORE indicates that the state of the turnout does not affect the route.

For Main 1 East. the most favorable indication for normal is Clear, reverse is Stop, and branch is Slow Clear. 

   // AFTON                                                                      CP_AFTON
{ .state = { SW_NORMAL, SW_NORMAL, SW_NORMAL }, .aspect = CLEAR }, // M1 EAST NORMAL 16 2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M1 EAST REVERSE
{ .state = { SW_NORMAL, SW_NORMAL, SW_REVERSE }, .aspect = SLOW_CLEAR }, // M1 EAST BRANCH

Here are the entries for Main 1 West. The most favorable indication for normal is Clear, reverse is Limited Clear, and branch is Stop.

   { .state = { SW_NORMAL,  SW_NORMAL,  SW_NORMAL  }, .aspect = CLEAR       }, // M1 WEST NORMAL  17      2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = LIMITED_CLR }, // M1 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_REVERSE }, .aspect = STOP }, // M1 WEST BRANCH

Here are the entries for Main 2 East. The most favorable indication for normal is Clear, reverse is Limited Clear, and branch is Slow Clear.

   { .state = { SW_NORMAL,  SW_NORMAL,  SW_IGNORE  }, .aspect = CLEAR       }, // M2 EAST NORMAL  18      2
{ .state = { SW_REVERSE, SW_REVERSE, SW_NORMAL }, .aspect = LIMITED_CLR }, // M2 EAST REVERSE
{ .state = { SW_REVERSE, SW_REVERSE, SW_REVERSE }, .aspect = SLOW_CLEAR }, // M2 EAST BRANCH

Here are the entries for Main 2 West. The most favorable indication for normal is Clear. Reverse and branch are Stop.

   { .state = { SW_NORMAL,  SW_NORMAL,  SW_IGNORE  }, .aspect = CLEAR       }, // M2 WEST NORMAL  19      2
{ .state = { SW_REVERSE, SW_REVERSE, SW_IGNORE }, .aspect = STOP }, // M2 WEST REVERSE
{ .state = { SW_IGNORE, SW_IGNORE, SW_IGNORE }, .aspect = STOP }, // M2 WEST BRANCH

Every control point has the same set of entries for the turnouts, and for each track and each direction. The signal logic processing checks each track and each direction for a given control point and saves the computed indication in the Route Status table. The Route Status table deals only with the indication for the associated control point.

The next signal processing steps check how each control point relates to it's adjacent control points.

Let me know if you have any questions. To Be Continued.

 

Reply 0
hoyden

No Joy

I tried rotating them but the setting doesn't seem to affect the pictures in the post.

Fixed! When I looked at the picture in my Account File Browser the orientation was correct. Then I deleted the picture in the blog post and reloaded it from the MRH file server. Wala...it fixed.

Computers...

Reply 0
hoyden

Computing the displayed indication

From the previous post I described how each CP computes its route status based on the states of the turnouts (normal, reverse, or branch) in the CP and the block occupancy for the home block and the next block beyond the home block in a given direction. The route status will be Stop if the turnouts are lined against the movement, or if either the home block, also called the OS, or On Sheet, by the prototypes, or the next block in the direction of traffic are occupied. Thus the home signal protects the home block and the next block beyond the home signal up to the point of the next CP. 

Prototypes call the home block the OS because back in the days of manual block, the block operator would write down the time and direction for each train that passed the block station. The train was "On Sheet" when it passed the block station. This information was passed on via telegraph to the next block station.

In modern signaling the signal hardware records the presence of a train in the home block and in the block beyond the home block. I call this indication the Route Status. i call the actual signal indication being displayed by the home signal the Display Status. 

My signal system does the exact same operation. For example, let's look at traffic moving eastbound on Main 1 through Afton on the normal route. If CP Afton Route Status is Clear then CP Afton checks the Display Status at the next CP, Sheldon Junction Main 1 east (shortened to M1E). Here is how that information is used:

  • If CP Sheldon Junction M1E is displaying Stop then set CP Afton M1E Display Status to Approach.
  • If CP Sheldon Junction M1E is displaying Approach then set CP Afton M1E Display Status to Advanced Approach.
  • If CP Sheldon Junction M1E is displaying Advanced Approach then set CP Afton M1E to Display Status to  Clear.
  • If CP Sheldon Junction M1E is displaying Medium Clear (we're being crossed over to Main 2) then set CP Afton M1E Display Status to Approach Medium.

The CP at Afton M1E upgrades the indication to give the locomotive engineer an advanced indication of what is happening ahead. This same logic is applied for every route through Afton on both tracks and in both directions.

If the CP Afton crossover is lined reverse then similar logic is applied. For CP Afton Main 1 west (M1E is always at Stop when the crossover is reverse) then check the home block for CP Afton Main 1, Main 2, and the next block west on Main 2. If any are occupied then set CP Afton M1W Route Status and Display Status to Stop.

When the crossover is reversed then CP Afton M1W most favorable indication is Limited Clear because the turnouts are #8. The next CP to the west of Afton is North Linden and CP Afton performs the same checks for North Linden.

This is a lot of processing to keep track of. I encapsulated all this information in the System Table and Route Status tables. The software iterates through each CP for each track Main 1 and Main 2, and for each direction East and West. Computers are really good at doing repetitive operations and the signal system is chock full of repetitive operations.

Any questions before proceeding further?

Reply 0
hoyden

The Problem with Signal Documentation

The problem with documenting the signal design is that all of the necessary documentation is Too Much Information (TMI). The good news is each bit of information is fundamentally simple so the key understanding the signal design is to understand the the basic constituent artifacts before trying to understand how the signal system uses those artifacts.

Although ML&S is built on top of Digitrax LocoNet the design is modular and can be adapted to other layout protocols or hardware that provide the same information.

The two primary artifacts that drive the signal system are the turnout state (normal or reverse) and block occupancy (occupied or clear). Turnout state can be read as a hardware discrete input from electrical switches mounted on the switch machine. I used the hardware signal in my original hardware design. That means there will be one hardware input for each turnout.

I obviated the need to read the switch state by monitoring LocoNet and listening for the turnout command that sets a turnout normal or reverse. The weakness of this approach is that sometimes turnout motors don't respond to the turnout command, or the switch that reports the turnout state goes out of correspondence with the turnout point rails. The signal system won't detect this fault without further mitigation to verify the correct position.

Fortunately these events are fairly rare but they do happen on my layout occasionally. I've found that the Tortoise switch machines are more reliable than the solenoid switch machines. I used a combination of Peco and NJ International solenoid switch machines. I have a long term capital improvement project to replace the solenoid machines with Tortoises. 

The problem I wrestle with is given a choice; run trains or replace switch machines, I tend to run trains. I do have one ace in the hole when I am compelled to work on the layout rather than run trains that I will document on the  motherpage  Minneapolis, Louisville & Southern

Reply 0
Reply