1 #*****************************************************************************
5 # Object Name : $RCSfile$
6 # Revision : $Revision$
9 # Created By : Robert Heller
10 # Created : Tue Jul 14 19:03:09 2015
11 # Last Modified : <150726.1010>
19 #*****************************************************************************
21 # Copyright (C) 2015 Robert Heller D/B/A Deepwoods Software
23 # Wendell, MA 01379-9728
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.
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.
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.
41 #*****************************************************************************
43 package require Azatrax;# require the Azatrax
package
44 package require snit;# require the SNIT OO framework
48 # @brief Switch (turnout) operation using 1/2 of a SR4
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
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.)
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
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.
83 # Then in the Main Loop, you would have:
86 # MainWindow ctcpanel invoke Switch1
87 # MainWindow ctcpanel invoke Switch2
88 # MainWindow ctcpanel invoke SwitchPlate1
89 # MainWindow ctcpanel invoke SwitchPlate2
90 # update;# Update display
94 # @author Robert Heller \<heller\@deepsoft.com\>
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}
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
117 option -forwarddirection \
118 -type {snit::enum -values {forward reverse}} -
default forward \
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 {}
136 ## @private Motor device (SR4 outputs)
138 ## @private Point sense device (SR4 inputs)
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
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
152 # @param object Some object.
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)
161 error
"$object is not a $type";#
object is something
else
166 ## @brief Constructor: initialize the switch object.
168 # Create a low level sensor object and install it as a component.
169 # Install the switch's signals, motor, and point sense objects.
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
176 # @arg -pointsenseobj Object (SR4) that senses the point state.
177 # @arg -pointsensehalf Which half: lower means I1 and I2, upper means
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
200 # @arg -plate The name of the switch plate for this switch.
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!"
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]
216 error
"The -motor option is required!"
218 set pointsense [$self cget -pointsenseobj]
219 set forwardsignal [$self cget -forwardsignalobj]
220 set reversemainsignal [$self cget -reversemainsignalobj]
221 set reversedivergentsignal [$self cget -reversedivergentsignalobj]
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) {
230 set options($option) $value
234 forward {set options($option) reverse}
235 reverse {set options($option) forward}
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.
245 switch $options(-forwarddirection) {
246 forward {
return $options($option)}
248 switch $options($option) {
249 forward {
return reverse}
250 reverse {
return forward}
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.
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.
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.
275 $ossensor ResetStopwatch
281 } elseif {$ossensor Sense_2} {
282 # At sense 2 -- just leaving
286 $ossensor ResetStopwatch
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.
297 } elseif {[$ossensor Latch_2]} {
298 # Gone past sense 2, but not yet at sense 1.
300 } elseif {[$ossensor Sense_1]} {
301 # Just leaving the switch at the point end.
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.
316 return no;# Just a guess!
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
326 # @returns Normal or reverse, indicating the point state.
328 # Assume point state is unknown.
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.
335 if {[$self cget -motorhalf] eq
"lower"} {
336 if {[$motor Q1_State]} {
338 } elseif {[$motor Q2_State]} {
344 if {[$motor Q3_State]} {
346 } elseif {[$motor Q4_State]} {
354 $pointsense GetStateData
355 if {[$self cget -pointsensehalf] eq
"lower"} {
356 if {[$pointsense Sense_1_Live]} {
358 } elseif {[$pointsense Sense_2_Live]} {
364 if {[$pointsense Sense_3_Live]} {
366 } elseif {[$pointsense Sense_4_Live]} {
373 if {[$self cget -plate] ne {}} {
374 set plate [$self cget -plate]
377 MainWindow ctcpanel seti $plate N on
378 MainWindow ctcpanel seti $plate C off
379 MainWindow ctcpanel seti $plate R off
382 MainWindow ctcpanel seti $plate N off
383 MainWindow ctcpanel seti $plate C off
384 MainWindow ctcpanel seti $plate R on
387 MainWindow ctcpanel seti $plate N off
388 MainWindow ctcpanel seti $plate C on
389 MainWindow ctcpanel seti $plate R off
396 ## @private Route check validation object.
398 set _routes [snit::enum _routes -values {normal reverse}]
401 method motor {route} {
402 ## The motor method sets the switch motor to align the points for the
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.
408 $_routes validate $route
411 if {[$self cget -motorhalf] eq
"lower"} {
412 $motor RelaysOff 1 1 0 0
413 $motor RelaysOn 1 0 0 0
415 $motor RelaysOff 0 0 1 1
416 $motor RelaysOn 0 0 1 0
418 if {$reversedivergentsignal ne {}} {
419 $reversedivergentsignal setaspect red
423 if {[$self cget -motorhalf] eq
"lower"} {
424 $motor RelaysOff 1 1 0 0
425 $motor RelaysOn 0 1 0 0
427 $motor RelaysOff 0 0 1 1
428 $motor RelaysOn 0 0 0 1
430 if {$reversemainsignal ne {}} {
431 $reversemainsignal setaspect red
436 method _entering {} {
437 ## @protected Code to run when just entering the OS
438 # Sets the signal aspects and propagates signal state.
440 switch $options(-direction) {
442 # Forward direction, set point end signal and propagate back
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]
451 switch [$self pointstate] {
453 # Set the main frog end signal and propagate down the
455 if {$reversemainsignal ne {}} {
456 $reversemainsignal setaspect red
458 if {[$self cget -nextmainblock] ne {}} {
459 [$self cget -nextmainblock] propagate yellow $self -direction [$self cget -direction]
463 # Set the divergent frog end signal and propagate down
464 # the divergent route.
465 if {$reversedivergentsignal ne {}} {
466 $reversedivergentsignal setaspect red
468 if {[$self cget -nextdivergentblock] ne {}} {
469 [$self cget -nextdivergentblock] propagate yellow $self -direction [$self cget -direction]
477 ## @protected Code to run when about to exit the OS
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.
486 set from [regsub {^::} $from {}]
487 $self configurelist $args
488 if {[$self occupiedp]} {
return}
490 switch $options(-direction) {
492 # Propagate back from the points.
493 switch [$self pointstate] {
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]
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]
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]
520 # Reverse direction, propagate towards the frog end.
521 switch [$self pointstate] {
523 # Points normal, propagate down the main.
524 if {$reversemainsignal ne {}} {
525 $reversemainsignal setaspect $aspect
527 if {$aspect eq
"yellow"} {
528 if {[$self cget -nextmainblock] ne {}} {
529 [$self cget -nextmainblock] propagate green $self -direction [$self cget -direction]
534 # Points reversed, propagate down the divergent route.
535 if {$reversedivergentsignal ne {}} {
536 $reversedivergentsignal setaspect $aspect
538 if {$aspect eq
"yellow"} {
539 if {[$self cget -nextdivergentblock] ne {}} {
540 [$self cget -nextdivergentblock] propagate green $self -direction [$self cget -direction]
551 package provide SR4_MRD2_Switch 1.0
Switch (turnout) operation using 1/2 of a SR4.