Model Railroad System  2.2.1
C4TSMINI_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 : Fri Jul 24 20:07:30 2015
11 # Last Modified : <150816.1126>
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 
44 package require CmriSupport;# require the Cmri Support package
45 package require snit;# require the SNIT OO framework
46 
47 snit::type C4TSMINI_Switch {
48  ##
49  # @brief Switch (turnout) operation using a Chubb SMINI board and a Circuits4Tracks Quad OD for OS detection
50  #
51  # @image html switch-C4TSMINI-thumb.png
52  # @image latex switch-C4TSMINI.png "Switch controlled by a Chubb SMINI board with a Circuits4Tracks OS detection" width=5in
53  #
54  # Above is a typical switch (turnout) using an Chubb SMINI board to
55  # control a Circuitron Tortoise Switch Machine and to sense the point
56  # position and a Circuits4Track quad occupancy detector to sense
57  # occupation of the switch.
58  #
59  # Typical usage:
60  #
61  # @code
62  # # Connect to the cmribus through a USB RS485 adapter at /dev/ttyUSB0
63  # CmriSupport::CmriNode openport /dev/ttyUSB0
64  # # SMINI board at address 0
65  # CmriSupport::CmriNode SMINI0 -type SMINI -address 0
66  # # Switch 1 is controled by bits 0 and 1 of output port 0
67  # # Switch 1 points are sensed by bits 0 and 1 of input port 0
68  # # Switch 1 OS is detected on bit 0 of input port 1
69  # C4TSMINI_Switch switch1 -nodeobj SMINI0 -motorport 0 -motorbit 0 \
70  # -pointsenseport 0 -pointsensebit 0 \
71  # -plate SwitchPlate1 \
72  # -ossensorport 1 -osbit 0
73  # # Switch 2 is controled by bits 0 and 1 of output port 1
74  # # Switch 2 points are sensed by bits 2 and 3 of input port 0
75  # # Switch 2 OS is detected on bit 1 of input port 1
76  # C4TSMINI_Switch switch2 -nodeobj SMINI0 -motorport 1 -motorbit 0 \
77  # -pointsenseport 0 -pointsensebit 2 \
78  # -plate SwitchPlate2 \
79  # -ossensorport 1 -osbit 1
80  # @endcode
81  #
82  # For the track work elements use "switchN occupiedp" for the track work
83  # elements' occupied script and use "switchN pointstate" for the track
84  # work elements' state script. For the switch plate use
85  # "switchN motor normal" for the normal script and "switchN motor reverse"
86  # for the reverse script.
87  #
88  # Then in the Main Loop, you would have:
89  # @code
90  # while {true} {
91  # MainWindow ctcpanel invoke Switch1
92  # MainWindow ctcpanel invoke Switch2
93  # MainWindow ctcpanel invoke SwitchPlate1
94  # MainWindow ctcpanel invoke SwitchPlate2
95  # update;# Update display
96  # }
97  # @endcode
98  #
99  # @author Robert Heller \<heller\@deepsoft.com\>
100 
101  # SMINI related options
102  # Node object
103  option -nodeobj -readonly yes -default {}
104  # Output port and bits for motor control
105  option -motorport -readonly yes -default 0 -type {snit::integer -min 0}
106  option -motorbit -readonly yes -default 0 -type {snit::integer -min 0 -max 6}
107  # Input port and bits for point sense
108  option -pointsenseport -readonly yes -default 0 -type {snit::integer -min 0}
109  option -pointsensebit -readonly yes -default 0 -type {snit::integer -min 0 -max 6}
110  # Input port and bit for OS sense
111  option -ossensorport -readonly yes -default 0 -type {snit::integer -min 0}
112  option -osbit -readonly yes -default 0 -type {snit::integer -min 0 -max 7}
113 
114  # Signal related options
115  # The forward direction means entering at the point end.
116  option -direction -type {snit::enum -values {forward reverse}} \
117  -default forward -configuremethod _settruedirection \
118  -cgetmethod _gettruedirection
119  # If the switch is installed opposite the overall traffic flow (eg it is
120  # a frog facing switch), then -forwarddirection needs to be set for
121  # reverse operation.
122  option -forwarddirection \
123  -type {snit::enum -values {forward reverse}} -default forward \
124  -readonly yes
125  # The forward signal is the signal protecting the points
126  option -forwardsignalobj -readonly yes -default {}
127  # The previous block is the block connected to the points
128  option -previousblock -default {}
129  # The reverse main signal is the signal protecting the straight frog end
130  option -reversemainsignalobj -readonly yes -default {}
131  # The next main block is the block connected to the main frog end
132  option -nextmainblock -default {}
133  # The reverse divergent signal is the signal protecting the divergent frog end
134  option -reversedivergentsignalobj -readonly yes -default {}
135  # The next divergent block is the block connected to the divergent frog end
136  option -nextdivergentblock -default {}
137  # Switch Plate name (if any).
138  option -plate -default {}
139 
140  component node
141  ## @private SMINI node object
142  variable isoccupied no
143  ## @private Saved occupation state.
144  typevariable _motorbits -array {
145  normal 0x01
146  reverse 0x02
147  }
148  ## @private Motor bit values
149  typevariable _pointsense -array {
150  0x00 unknown
151  0x01 normal
152  0x02 reverse
153  0x03 unknown
154  }
155  ## @private Point sense bit values
156 
157  typemethod validate {object} {
158  ## Type validating code
159  # Raises an error if object is not either the empty string or a SR4_C4TSR4_Switch
160  # type.
161  # @param object Some object.
162 
163  if {$object eq ""} {
164  return $object;# Empty or null objects are OK
165  } elseif {[catch {$object info type} itstype]} {
166  error "$object is not a $type";# object is not a SNIT type
167  } elseif {$itstype eq $type} {
168  return $object;# Object is of our type (SR4_C4TSR4_Switch)
169  } else {
170  error "$object is not a $type";# object is something else
171  }
172  }
173 
174  constructor {args} {
175  ## @brief Constructor: initialize the switch object.
176  #
177  # Create a low level sensor object and install it as a component.
178  # Install the switch's signals, motor, and point sense objects.
179  #
180  # @param name Name of the switch object
181  # @param ... Options:
182  # @arg -nodeobj Cmri node object
183  # @arg -motorport Output port number for motor control.
184  # @arg -motorbit First (of two) motor control bits.
185  # @arg -pointsenseport Input port for point sense.
186  # @arg -pointsensebit First (of two) point sense bits.
187  # @arg -ossensorport Input port for OS sense.
188  # @arg -osbit This defines the input bit on the input port for OS
189  # sense.
190  # @arg -direction The current direction of travel. Forward always
191  # means entering at the point end.
192  # @arg -forwarddirection The @e logial forward direction. Set this
193  # to reverse for a frog facing switch. Default is forward and it
194  # is readonly and can only be set during creation.
195  # @arg -forwardsignalobj The signal object protecting the points.
196  # Presumed to be a two headed signal, with the upper head relating to
197  # the main (straight) route and the lower head relating to the
198  # divergent route. The upper head has three colors: red, yellow, and
199  # green. The lower head only two: red and green.
200  # @arg -reversemainsignalobj The signal object protecting the straight
201  # frog end. Presumed to be single headed (with number plate).
202  # @arg -reversedivergentsignalobj The signal object protecting the
203  # divergent frog end. Presumed to be single headed (with number plate).
204  # @arg -previousblock The block connected to the point end.
205  # @arg -nextmainblock The block connected to the straight frog end.
206  # @arg -nextdivergentblock The block connected to the divergent frog
207  # end.
208  # @arg -plate The name of the switch plate for this switch.
209  # @par
210 
211  # Prefetch the -forwarddirection option.
212  set options(-forwarddirection) [from args -forwarddirection]
213  ## Process any other options
214  $self configurelist $args
215  set node [$self cget -nodeobj]
216  if {$node eq {}} {
217  error "The -nodeobj option is required!"
218  }
219  CmriSupport::CmriNode validate $node
220  set forwardsignal [$self cget -forwardsignalobj]
221  set reversemainsignal [$self cget -reversemainsignalobj]
222  set reversedivergentsignal [$self cget -reversedivergentsignalobj]
223  }
224 
225  method _settruedirection {option value} {
226  ## @private A method to fake direction for frog facing switches.
227  # @param option This is always -direction.
228  # @param value Either forward or reverse.
229  switch $options(-forwarddirection) {
230  forward {
231  set options($option) $value
232  }
233  reverse {
234  switch $value {
235  forward {set options($option) reverse}
236  reverse {set options($option) forward}
237  }
238  }
239  }
240  }
241  method _gettruedirection {option} {
242  ## @private A method to fake direction for frog facing switches.
243  # @param option This is always -direction.
244  # @returns Either forward or reverse.
245 
246  switch $options(-forwarddirection) {
247  forward {return $options($option)}
248  reverse {
249  switch $options($option) {
250  forward {return reverse}
251  reverse {return forward}
252  }
253  }
254  }
255  }
256 
257  method occupiedp {} {
258  ## The occupiedp method returns yes or no (true or false) indicating
259  # block (OS) occupation.
260  # @returns Yes or no, indicating whether the OS is occupied.
261 
262  # First read the current sensor state.
263  set inputs [$node inputs]
264  set port [lindex $inputs [$self cget -ossensorport]]
265  set bit [expr {($port >> [$self cget -osbit]) & 0x01}]
266  # The outputs of the OD are active negative, so we need to invert the
267  # logic.
268  set bit [expr {(~$bit) & 0x01}]
269  if {$bit == 1} {
270  if {$isoccupied} {
271  # Already entered the OS.
272  return $isoccupied
273  } else {
274  # Just entered the OS
275  set isoccupied yes
276  $self _entering
277  return $isoccupied
278  }
279  } else {
280  if {$isoccupied} {
281  # Just left the OS
282  set isoccupied no
283  $self _exiting
284  return $isoccupied
285  } else {
286  # OS still unoccupied
287  return $isoccupied
288  }
289  }
290  }
291  method pointstate {} {
292  ## The pointstate method returns normal if the points are aligned to
293  # the main route and reverse if the points are aligned to the divergent
294  # route. If the state cannot be determined, a value of unknown is
295  # returned.
296  # @returns Normal or reverse, indicating the point state.
297 
298  # Assume point state is unknown.
299  set result unknown
300  set inputs [$node inputs]
301  set port [lindex $inputs [$self cget -pointsenseport]]
302  set bits [expr {($port >> [$self cget -pointsensebit]) & 0x03}]
303  set result $_pointsense($bits)
304  if {[$self cget -plate] ne {}} {
305  set plate [$self cget -plate]
306  switch $result {
307  normal {
308  MainWindow ctcpanel seti $plate N on
309  MainWindow ctcpanel seti $plate C off
310  MainWindow ctcpanel seti $plate R off
311  }
312  reverse {
313  MainWindow ctcpanel seti $plate N off
314  MainWindow ctcpanel seti $plate C off
315  MainWindow ctcpanel seti $plate R on
316  }
317  unknown {
318  MainWindow ctcpanel seti $plate N off
319  MainWindow ctcpanel seti $plate C on
320  MainWindow ctcpanel seti $plate R off
321  }
322  }
323  }
324  return $result
325  }
326  typevariable _routes
327  ## @private Route check validation object.
328  typeconstructor {
329  set _routes [snit::enum _routes -values {normal reverse}]
330  }
331 
332  method motor {route} {
333  ## The motor method sets the switch motor to align the points for the
334  # specificed route.
335  # @param route The desired route. A value of normal means align the
336  # points to the main (straight) route and a value of reverse means
337  # align the points to the divergent route.
338 
339  $_routes validate $route
340  set mask [expr {0x03 << [$self cget -motorbit]}]
341  set bits [expr {$_motorbits($route) << [$self cget -motorbit]}]
342  $node setbitfield [$self cget -motorport] $mask $bits
343  }
344  method _entering {} {
345  ## @protected Code to run when just entering the OS
346  # Sets the signal aspects and propagates signal state.
347 
348  switch $options(-direction) {
349  forward {
350  # Forward direction, set point end signal and propagate back
351  # from the points.
352  if {$forwardsignal ne {}} {$forwardsignal setaspect {red red}}
353  if {[$self cget -previousblock] ne {}} {
354  [$self cget -previousblock] propagate yellow $self -direction [$self cget -direction]
355  }
356  }
357  reverse {
358  # Reverse direction.
359  switch [$self pointstate] {
360  normal {
361  # Set the main frog end signal and propagate down the
362  # main.
363  if {$reversemainsignal ne {}} {
364  $reversemainsignal setaspect red
365  }
366  if {[$self cget -nextmainblock] ne {}} {
367  [$self cget -nextmainblock] propagate yellow $self -direction [$self cget -direction]
368  }
369  }
370  reverse {
371  # Set the divergent frog end signal and propagate down
372  # the divergent route.
373  if {$reversedivergentsignal ne {}} {
374  $reversedivergentsignal setaspect red
375  }
376  if {[$self cget -nextdivergentblock] ne {}} {
377  [$self cget -nextdivergentblock] propagate yellow $self -direction [$self cget -direction]
378  }
379  }
380  }
381  }
382  }
383  }
384  method _exiting {} {
385  ## @protected Code to run when about to exit the OS
386  }
387  method propagate {aspect from args} {
388  ## @publicsection Method used to propagate distant signal states back down the line.
389  # @param aspect The signal aspect that is being propagated.
390  # @param from The propagating block.
391  # @param ... Options:
392  # @arg -direction The direction of the propagation.
393 
394  set from [regsub {^::} $from {}]
395  $self configurelist $args
396  if {[$self occupiedp]} {return}
397 
398  switch $options(-direction) {
399  forward {
400  # Propagate back from the points.
401  switch [$self pointstate] {
402  normal {
403  # Points are normal, upper head is a logical block
404  # signal, but don't propagate against the points.
405  if {$from ne [regsub {^::} [$self cget -nextmainblock] {}]} {return}
406  if {$forwardsignal ne {}} {
407  $forwardsignal setaspect [list $aspect red]
408  }
409  }
410  reverse {
411  # Points are reversed, lower head is the controling
412  # head, but has no yellow.
413  # But don't propagate against the points.
414  if {$from ne [regsub {^::} [$self cget -nextdivergentblock] {}]} {return}
415  if {$forwardsignal ne {}} {
416  $forwardsignal setaspect [list red green]
417  }
418  }
419  }
420  # Propagate back from the points.
421  if {$aspect eq "yellow"} {
422  if {[$self cget -previousblock] ne {}} {
423  [$self cget -previousblock] propagate green $self -direction [$self cget -direction]
424  }
425  }
426  }
427  reverse {
428  # Reverse direction, propagate towards the frog end.
429  switch [$self pointstate] {
430  normal {
431  # Points normal, propagate down the main.
432  if {$reversemainsignal ne {}} {
433  $reversemainsignal setaspect $aspect
434  }
435  if {$aspect eq "yellow"} {
436  if {[$self cget -nextmainblock] ne {}} {
437  [$self cget -nextmainblock] propagate green $self -direction [$self cget -direction]
438  }
439  }
440  }
441  reverse {
442  # Points reversed, propagate down the divergent route.
443  if {$reversedivergentsignal ne {}} {
444  $reversedivergentsignal setaspect $aspect
445  }
446  if {$aspect eq "yellow"} {
447  if {[$self cget -nextdivergentblock] ne {}} {
448  [$self cget -nextdivergentblock] propagate green $self -direction [$self cget -direction]
449  }
450  }
451  }
452  }
453  }
454  }
455  }
456 }
457 
458 package provide C4TSMINI_Switch 1.0
C4TSMINI_Switch
Switch (turnout) operation using a Chubb SMINI board and a Circuits4Tracks Quad OD for OS detection.
Definition: C4TSMINI_Switch.tcl:57
SR4_C4TSR4_Switch
Switch (turnout) operation using 1/2 of a SR4.
Definition: SR4_C4TSR4_Switch.tcl:57