Model Railroad System  2.2.1
SR4_MRD2_Switch.tcl
1 #*****************************************************************************
2 #
3 # System :
4 # Module :
5 # Object Name : $RCSfile$
6 # Revision : $Revision$
7 # Date : $Date$
8 # Author : $Author$
9 # Created By : Robert Heller
10 # Created : Tue Jul 14 19:03:09 2015
11 # Last Modified : <150726.1010>
12 #
13 # Description
14 #
15 # Notes
16 #
17 # History
18 #
19 #*****************************************************************************
20 #
21 # Copyright (C) 2015 Robert Heller D/B/A Deepwoods Software
22 # 51 Locke Hill Road
23 # Wendell, MA 01379-9728
24 #
25 # This program is free software; you can redistribute it and/or modify
26 # it under the terms of the GNU General Public License as published by
27 # the Free Software Foundation; either version 2 of the License, or
28 # (at your option) any later version.
29 #
30 # This program is distributed in the hope that it will be useful,
31 # but WITHOUT ANY WARRANTY; without even the implied warranty of
32 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 # GNU General Public License for more details.
34 #
35 # You should have received a copy of the GNU General Public License
36 # along with this program; if not, write to the Free Software
37 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
38 #
39 #
40 #
41 #*****************************************************************************
42 
43 package require Azatrax;# require the Azatrax package
44 package require snit;# require the SNIT OO framework
45 
46 snit::type SR4_MRD2_Switch {
47  ##
48  # @brief Switch (turnout) operation using 1/2 of a SR4
49  #
50  # @image html switch-SR4-MRD2-thumb.png
51  # @image latex switch-SR4-MRD2.png "Switch controlled by a SR4 with MRD2 OS Detection" width=5in
52  #
53  # Above is a typical switch (turnout) using an Azatrax SR4 to control a
54  # Circuitron Tortoise Switch Machine and to sense the point position and
55  # an Azatrax MRD2 to sense occupation of the switch.
56  # (A high resolution PDF and a Xtrkcad layout file are included.)
57  #
58  # Typical usage:
59  #
60  # @code
61  # SR4 turnoutControl1 \
62  # -this [Azatrax_OpenDevice 0400001234 $::Azatrax_idSR4Product]
63  # # Disable inputs controlling outputs.
64  # turnoutControl1 OutputRelayInputControl 0 0 0 0
65  # # Switch 1 is controlled and sensed by the lower 1/2 of turnoutControl1
66  # SR4_MRD2_Switch switch1 -motorobj turnoutControl1 -motorhalf lower \
67  # -pointsenseobj turnoutControl1 \
68  # -pointsensehalf lower -plate SwitchPlate1 \
69  # -ossensorsn 0200001234
70  # # Switch2 is controlled and sensed by the upper 1/2 of turnoutControl1
71  # SR4_MRD2_Switch switch2 -motorobj turnoutControl1 -motorhalf upper \
72  # -pointsenseobj turnoutControl1 \
73  # -pointsensehalf upper -plate SwitchPlate2 \
74  # -ossensorsn 0200001235
75  # @endcode
76  #
77  # For the track work elements use "switchN occupiedp" for the track work
78  # elements' occupied script and use "switchN pointstate" for the track
79  # work elements' state script. For the switch plate use
80  # "switchN motor normal" for the normal script and "switchN motor reverse"
81  # for the reverse script.
82  #
83  # Then in the Main Loop, you would have:
84  # @code
85  # while {true} {
86  # MainWindow ctcpanel invoke Switch1
87  # MainWindow ctcpanel invoke Switch2
88  # MainWindow ctcpanel invoke SwitchPlate1
89  # MainWindow ctcpanel invoke SwitchPlate2
90  # update;# Update display
91  # }
92  # @endcode
93  #
94  # @author Robert Heller \<heller\@deepsoft.com\>
95 
96  # Azatrax related options
97  # Motor control (SR4 relays)
98  option -motorobj -readonly yes -default {}
99  option -motorhalf -readonly yes -default lower \
100  -type {snit::enum -values {lower upper}}
101  # Point sense (SR4 inputs)
102  option -pointsenseobj -readonly yes -default {}
103  option -pointsensehalf -readonly yes -default lower \
104  -type {snit::enum -values {lower upper}}
105  # Occupency sensor (MRD2)
106  option -ossensorsn -readonly yes -default {}
107  option -diverttimeout -default 10.0 -type {snit:: -min 1.0 -max 60.0}
108 
109  # Signal related options
110  # The forward direction means entering at the point end.
111  option -direction -type {snit::enum -values {forward reverse}} \
112  -default forward -configuremethod _settruedirection \
113  -cgetmethod _gettruedirection
114  # If the switch is installed opposite the overall traffic flow (eg it is
115  # a frog facing switch), then -forwarddirection needs to be set for
116  # reverse operation.
117  option -forwarddirection \
118  -type {snit::enum -values {forward reverse}} -default forward \
119  -readonly yes
120  # The forward signal is the signal protecting the points
121  option -forwardsignalobj -readonly yes -default {}
122  # The previous block is the block connected to the points
123  option -previousblock -default {}
124  # The reverse main signal is the signal protecting the straight frog end
125  option -reversemainsignalobj -readonly yes -default {}
126  # The next main block is the block connected to the main frog end
127  option -nextmainblock -default {}
128  # The reverse divergent signal is the signal protecting the divergent frog end
129  option -reversedivergentsignalobj -readonly yes -default {}
130  # The next divergent block is the block connected to the divergent frog end
131  option -nextdivergentblock -default {}
132  # Switch Plate name (if any).
133  option -plate -default {}
134 
135  component motor
136  ## @private Motor device (SR4 outputs)
137  component pointsense
138  ## @private Point sense device (SR4 inputs)
139  component ossensor
140  ## @private Occupency sensor (MRD2)
141  component forwardsignal
142  ## @private Signal at the points
143  component reversemainsignal
144  ## @private Signal at the straight frog end
145  component reversedivergentsignal
146  ## @private Signal at the divergent frog end
147 
148  typemethod validate {object} {
149  ## Type validating code
150  # Raises an error if object is not either the empty string or a SR4_MRD2_Switch
151  # type.
152  # @param object Some object.
153 
154  if {$object eq ""} {
155  return $object;# Empty or null objects are OK
156  } elseif {[catch {$object info type} itstype]} {
157  error "$object is not a $type";# object is not a SNIT type
158  } elseif {$itstype eq $type} {
159  return $object;# Object is of our type (Block)
160  } else {
161  error "$object is not a $type";# object is something else
162  }
163  }
164 
165  constructor {args} {
166  ## @brief Constructor: initialize the switch object.
167  #
168  # Create a low level sensor object and install it as a component.
169  # Install the switch's signals, motor, and point sense objects.
170  #
171  # @param name Name of the switch object
172  # @param ... Options:
173  # @arg -motorobj Object (SR4) that controls the motor.
174  # @arg -motorhalf Which half: lower means Q1 and Q2, upper means Q3
175  # and Q4.
176  # @arg -pointsenseobj Object (SR4) that senses the point state.
177  # @arg -pointsensehalf Which half: lower means I1 and I2, upper means
178  # I3 and I4.
179  # @arg -ossensorsn Serial number of the MRD2 that is sensing OS.
180  # @arg -diverttimeout Timeout, in seconds to allow for a train to
181  # clear the turnout when going on a divergent route.
182  # @arg -direction The current direction of travel. Forward always
183  # means entering at the point end.
184  # @arg -forwarddirection The @e logial forward direction. Set this
185  # to reverse for a frog facing switch. Default is forward and it
186  # is readonly and can only be set during creation.
187  # @arg -forwardsignalobj The signal object protecting the points.
188  # Presumed to be a two headed signal, with the upper head relating to
189  # the main (straight) route and the lower head relating to the
190  # divergent route. The upper head has three colors: red, yellow, and
191  # green. The lower head only two: red and green.
192  # @arg -reversemainsignalobj The signal object protecting the straight
193  # frog end. Presumed to be single headed (with number plate).
194  # @arg -reversedivergentsignalobj The signal object protecting the
195  # divergent frog end. Presumed to be single headed (with number plate).
196  # @arg -previousblock The block connected to the point end.
197  # @arg -nextmainblock The block connected to the straight frog end.
198  # @arg -nextdivergentblock The block connected to the divergent frog
199  # end.
200  # @arg -plate The name of the switch plate for this switch.
201  #
202 
203  # Prefetch the -forwarddirection option.
204  set options(-forwarddirection) [from args -forwarddirection]
205  # Prefetch the MRD2U's serial number
206  set options(-ossensorsn) [from args -ossensorsn]
207  if {$options(-ossensorsn) eq {}} {
208  error "The -ossensorsn option is required!"
209  }
210  install ossensor using Azatrax_OpenDevice $options(-ossensorsn) \
211  $::Azatrax_idMRDProduct
212  ## Process any other options
213  $self configurelist $args
214  set motor [$self cget -motorobj]
215  if {$motor eq {}} {
216  error "The -motor option is required!"
217  }
218  set pointsense [$self cget -pointsenseobj]
219  set forwardsignal [$self cget -forwardsignalobj]
220  set reversemainsignal [$self cget -reversemainsignalobj]
221  set reversedivergentsignal [$self cget -reversedivergentsignalobj]
222  }
223 
224  method _settruedirection {option value} {
225  ## @private A method to fake direction for frog facing switches.
226  # @param option This is always -direction.
227  # @param value Either forward or reverse.
228  switch $options(-forwarddirection) {
229  forward {
230  set options($option) $value
231  }
232  reverse {
233  switch $value {
234  forward {set options($option) reverse}
235  reverse {set options($option) forward}
236  }
237  }
238  }
239  }
240  method _gettruedirection {option} {
241  ## @private A method to fake direction for frog facing switches.
242  # @param option This is always -direction.
243  # @returns Either forward or reverse.
244 
245  switch $options(-forwarddirection) {
246  forward {return $options($option)}
247  reverse {
248  switch $options($option) {
249  forward {return reverse}
250  reverse {return forward}
251  }
252  }
253  }
254  }
255 
256  method occupiedp {} {
257  ## The occupiedp method returns yes or no (true or false) indicating
258  # block (OS) occupation.
259  # @returns Yes or no, indicating whether the OS is occupied.
260 
261  $ossensor GetStateData;# Fetch state
262  if {$options(-direction) eq "forward"} {
263  if {[$ossensor Sense_1]} {
264  # Just entering the switch from the point end.
265  $self _entering
266  return yes
267  } elseif {[$ossensor Latch_1]} {
268  # Gone past sense 1, but not yet at sense 2.
269  $ossensor Stopwatch fract seconds minutes hours
270  set fseconds [expr {($fract/100.0)+$seconds+($minutes*60)+($hours*60*60)}]
271  if {[$self pointstate] eq "reverse" &&
272  $fseconds > [$self cget -diverttimeout]} {
273  # Divergent timeout: 'fake' a trip of sensor 2.
274  $ossensor SetChan2
275  $ossensor ResetStopwatch
276  $self _exiting
277  return no
278  } else {
279  return yes
280  }
281  } elseif {$ossensor Sense_2} {
282  # At sense 2 -- just leaving
283  $self _exiting
284  return yes
285  } else {
286  $ossensor ResetStopwatch
287  return no
288  }
289  } else {
290  # Reverse direction.
291  if {[$self pointstate] eq "normal"} {
292  # Points are normal -- entry is Sense_2, exit is Sense_1.
293  if {[$ossensor Sense_2]} {
294  # Just entering the switch from the main frog end.
295  $self _entering
296  return yes
297  } elseif {[$ossensor Latch_2]} {
298  # Gone past sense 2, but not yet at sense 1.
299  return yes
300  } elseif {[$ossensor Sense_1]} {
301  # Just leaving the switch at the point end.
302  $self _exiting
303  return yes
304  } else {
305  return no
306  }
307  } else {
308  # Points are reversed -- entry is not detectable, only exit!
309  if {[$ossensor Sense_1]} {
310  # Just leaving the switch at the point end.
311  # Fake an entrance and then exit.
312  $self _entering
313  $self _exiting
314  return yes
315  } else {
316  return no;# Just a guess!
317  }
318  }
319  }
320  }
321  method pointstate {} {
322  ## The pointstate method returns normal if the points are aligned to
323  # the main route and reverse if the points are aligned to the divergent
324  # route. If the state cannot be determined, a value of unknown is
325  # returned.
326  # @returns Normal or reverse, indicating the point state.
327 
328  # Assume point state is unknown.
329  set result unknown
330 
331  # Check for a point sensor. If none, use motor position instead.
332  if {$pointsense eq {}} {
333  # No point sense object -- use motor position instead.
334  $motor GetStateData
335  if {[$self cget -motorhalf] eq "lower"} {
336  if {[$motor Q1_State]} {
337  set result normal
338  } elseif {[$motor Q2_State]} {
339  set result reverse
340  } else {
341  set result unknown
342  }
343  } else {
344  if {[$motor Q3_State]} {
345  set result normal
346  } elseif {[$motor Q4_State]} {
347  set result reverse
348  } else {
349  set result unknown
350  }
351  }
352  } else {
353  # Fetch state
354  $pointsense GetStateData
355  if {[$self cget -pointsensehalf] eq "lower"} {
356  if {[$pointsense Sense_1_Live]} {
357  set result normal
358  } elseif {[$pointsense Sense_2_Live]} {
359  set result reverse
360  } else {
361  set result unknown
362  }
363  } else {
364  if {[$pointsense Sense_3_Live]} {
365  set result normal
366  } elseif {[$pointsense Sense_4_Live]} {
367  set result reverse
368  } else {
369  set resultunknown
370  }
371  }
372  }
373  if {[$self cget -plate] ne {}} {
374  set plate [$self cget -plate]
375  switch $result {
376  normal {
377  MainWindow ctcpanel seti $plate N on
378  MainWindow ctcpanel seti $plate C off
379  MainWindow ctcpanel seti $plate R off
380  }
381  reverse {
382  MainWindow ctcpanel seti $plate N off
383  MainWindow ctcpanel seti $plate C off
384  MainWindow ctcpanel seti $plate R on
385  }
386  unknown {
387  MainWindow ctcpanel seti $plate N off
388  MainWindow ctcpanel seti $plate C on
389  MainWindow ctcpanel seti $plate R off
390  }
391  }
392  }
393  return $result
394  }
395  typevariable _routes
396  ## @private Route check validation object.
397  typeconstructor {
398  set _routes [snit::enum _routes -values {normal reverse}]
399  }
400 
401  method motor {route} {
402  ## The motor method sets the switch motor to align the points for the
403  # specificed route.
404  # @param route The desired route. A value of normal means align the
405  # points to the main (straight) route and a value of reverse means
406  # align the points to the divergent route.
407 
408  $_routes validate $route
409  switch $route {
410  normal {
411  if {[$self cget -motorhalf] eq "lower"} {
412  $motor RelaysOff 1 1 0 0
413  $motor RelaysOn 1 0 0 0
414  } else {
415  $motor RelaysOff 0 0 1 1
416  $motor RelaysOn 0 0 1 0
417  }
418  if {$reversedivergentsignal ne {}} {
419  $reversedivergentsignal setaspect red
420  }
421  }
422  reverse {
423  if {[$self cget -motorhalf] eq "lower"} {
424  $motor RelaysOff 1 1 0 0
425  $motor RelaysOn 0 1 0 0
426  } else {
427  $motor RelaysOff 0 0 1 1
428  $motor RelaysOn 0 0 0 1
429  }
430  if {$reversemainsignal ne {}} {
431  $reversemainsignal setaspect red
432  }
433  }
434  }
435  }
436  method _entering {} {
437  ## @protected Code to run when just entering the OS
438  # Sets the signal aspects and propagates signal state.
439 
440  switch $options(-direction) {
441  forward {
442  # Forward direction, set point end signal and propagate back
443  # from the points.
444  if {$forwardsignal ne {}} {$forwardsignal setaspect {red red}}
445  if {[$self cget -previousblock] ne {}} {
446  [$self cget -previousblock] propagate yellow $self -direction [$self cget -direction]
447  }
448  }
449  reverse {
450  # Reverse direction.
451  switch [$self pointstate] {
452  normal {
453  # Set the main frog end signal and propagate down the
454  # main.
455  if {$reversemainsignal ne {}} {
456  $reversemainsignal setaspect red
457  }
458  if {[$self cget -nextmainblock] ne {}} {
459  [$self cget -nextmainblock] propagate yellow $self -direction [$self cget -direction]
460  }
461  }
462  reverse {
463  # Set the divergent frog end signal and propagate down
464  # the divergent route.
465  if {$reversedivergentsignal ne {}} {
466  $reversedivergentsignal setaspect red
467  }
468  if {[$self cget -nextdivergentblock] ne {}} {
469  [$self cget -nextdivergentblock] propagate yellow $self -direction [$self cget -direction]
470  }
471  }
472  }
473  }
474  }
475  }
476  method _exiting {} {
477  ## @protected Code to run when about to exit the OS
478  }
479  method propagate {aspect from args} {
480  ## @publicsection Method used to propagate distant signal states back down the line.
481  # @param aspect The signal aspect that is being propagated.
482  # @param from The propagating block.
483  # @param ... Options:
484  # @arg -direction The direction of the propagation.
485 
486  set from [regsub {^::} $from {}]
487  $self configurelist $args
488  if {[$self occupiedp]} {return}
489 
490  switch $options(-direction) {
491  forward {
492  # Propagate back from the points.
493  switch [$self pointstate] {
494  normal {
495  # Points are normal, upper head is a logical block
496  # signal, but don't propagate against the points.
497  if {$from ne [regsub {^::} [$self cget -nextmainblock] {}]} {return}
498  if {$forwardsignal ne {}} {
499  $forwardsignal setaspect [list $aspect red]
500  }
501  }
502  reverse {
503  # Points are reversed, lower head is the controling
504  # head, but has no yellow.
505  # But don't propagate against the points.
506  if {$from ne [regsub {^::} [$self cget -nextdivergentblock] {}]} {return}
507  if {$forwardsignal ne {}} {
508  $forwardsignal setaspect [list red green]
509  }
510  }
511  }
512  # Propagate back from the points.
513  if {$aspect eq "yellow"} {
514  if {[$self cget -previousblock] ne {}} {
515  [$self cget -previousblock] propagate green $self -direction [$self cget -direction]
516  }
517  }
518  }
519  reverse {
520  # Reverse direction, propagate towards the frog end.
521  switch [$self pointstate] {
522  normal {
523  # Points normal, propagate down the main.
524  if {$reversemainsignal ne {}} {
525  $reversemainsignal setaspect $aspect
526  }
527  if {$aspect eq "yellow"} {
528  if {[$self cget -nextmainblock] ne {}} {
529  [$self cget -nextmainblock] propagate green $self -direction [$self cget -direction]
530  }
531  }
532  }
533  reverse {
534  # Points reversed, propagate down the divergent route.
535  if {$reversedivergentsignal ne {}} {
536  $reversedivergentsignal setaspect $aspect
537  }
538  if {$aspect eq "yellow"} {
539  if {[$self cget -nextdivergentblock] ne {}} {
540  [$self cget -nextdivergentblock] propagate green $self -direction [$self cget -direction]
541  }
542  }
543  }
544  }
545  }
546  }
547  }
548 }
549 
550 
551 package provide SR4_MRD2_Switch 1.0
SR4_MRD2_Switch
Switch (turnout) operation using 1/2 of a SR4.
Definition: SR4_MRD2_Switch.tcl:53