chiark / gitweb /
control train 1
[trains.git] / hostside / stopgap-controller
1 #!/usr/bin/tclsh8.2
2 # used like this:
3 # liberator:hostside> ssh bessar 'cd things/trains-bessar/hostside && ./stopgap-controller' | ./gui-displayer -
4
5 set testonly 0
6 #set testonly 1
7 set port /dev/ttya0
8 #set port /dev/ttyS0
9
10 set loco 1
11
12 set ch(funcsevery) 10
13 set ch(speeddirnevery) 30
14 set ch(scale) 1
15
16 set ch(minint) 5000
17
18 #unset pointasked
19 # 0 1 (settings) M0 M1 (manual, settings) unset (random) M (manual)
20
21 set nmrawhich 0
22 set lastptchosen xx
23
24 set polmsg(l) 908000
25 set polmsg(x) 90f802
26 set polmsg(y) 90807c
27 set pname l
28 set m {}
29 set nmradiv 0
30 set segs {xx yy}
31 set segsasgot {xx yy}
32 set pq {} ;# unset: cdu charged and waiting
33 set speeddirn ff7f
34 #unset askspeedix
35 set askspeeds {10 30 50 80 100 126}
36 #set speeddirn ffff80c3fbcced7f
37 #set speeddirn_fixed {speed126 2 80 0}
38 set speeddirn_fixed {}
39 set funcs ff7f
40 # unset pointpos($point)
41 # unset segdetect($seg) ;# unset: shown D0; {}: shown D1; or: after id, D1->0
42
43 proc gui {m} {
44     puts "GUI $m"
45 }
46
47 proc gui_init {} {
48     global watchdog polarity segdetect
49     gui "M A2 0"
50     gui "M A5 0 J"
51     gui "M A6 0 J"
52     gui "EOE"
53     if {[info exists watchdog]} { gui "P 1" }
54     gui_polarity
55     foreach seg [array names segdetect] {
56         gui "D1 $seg"
57     }
58 }
59
60 proc debug {m} {
61     puts $m
62 }
63
64 proc tellpic_q {m} {
65     global p testonly
66     if {$testonly} return
67     set b [binary format H* $m]
68     puts -nonewline $p $b
69 }
70
71 proc tellpic {m} {
72     puts ">> $m"
73     tellpic_q $m
74 }
75
76 proc bgerror {m} {
77     if {[catch {
78         global errorInfo errorCode
79         puts stderr "$m\n$errorCode\n$errorInfo"
80         fail "bgerror $m"
81     } emsg]} {
82         exit 127
83     }
84 }
85
86 proc fail_now {} {
87     global p
88     debug "failing now"
89     fconfigure $p -blocking yes
90     gui "P 0"
91     tellpic 10
92     exit 1
93 }
94
95 proc fail {m} {
96     global watchdog p
97     catch { after cancel $watchdog; unset watchdog }
98     puts "failing $m"
99     tellpic 9801 ;# 16ms
100     after 1000 fail_now
101     fileevent $p readable {}
102 }
103
104 proc gui_polarity {} {
105     global pname
106     set 1 {}
107     switch -exact $pname {
108         l { lappend 0 X1 X3 X5 X7 X9; lappend 0 X2 X4 X6 X8 X10 }
109         x { lappend 1 X1 X3 X5 X7 X9; lappend 0 X2 X4 X6 X8 X10 }
110         y { lappend 0 X1 X3 X5 X7 X9; lappend 1 X2 X4 X6 X8 X10 }
111     }
112     foreach v {0 1} {
113         foreach seg [set $v] {
114             gui "R $v $seg"
115         }
116     }
117 }
118
119 proc polarity {newpname} {
120     global pname polmsg
121     debug "polarising $newpname"
122     if {![string compare $pname $newpname]} return
123     tellpic $polmsg($newpname)
124     set pname $newpname
125     gui_polarity
126 }
127
128 proc pt_now {how point pos xtra} {
129     set msg a0[lindex $point $pos]
130     debug "$how point $point pos=$pos msg=$msg$xtra"
131     gui "M [lindex $point 2] [expr {!$pos}]"
132     tellpic $msg
133 }
134 proc pt_must {point newpos} {
135     upvar #0 pointpos($point) pos
136     global pq
137     if {[info exists pos] && $pos == $newpos} return
138     set pos $newpos
139     if {[info exists pq]} {
140         lappend pq [list $point $pos]
141         debug "queue point $point pos=$pos l=[llength $pq]"
142         return
143     }
144     pt_now immed $point $pos {}
145     set pq {}
146 }
147 proc pt_ifthenmust {ifpoint ifposwant thenpoint thenpos} {
148     upvar #0 pointpos($ifpoint) ifpos
149     if {![info exists ifpos] || $ifpos != $ifposwant} return
150     pt_must $thenpoint $thenpos
151 }
152
153 proc badwatchdog {} {
154     global pq
155     puts "watchdog - oh well"
156     if {![info exists pq]} { set pq {} }
157 }
158
159 proc pm_charged {} {
160     global pq
161     if {[llength $pq]} {
162         set v [lindex $pq 0]
163         set pq [lrange $pq 1 end]
164         pt_now nowdo [lindex $v 0] [lindex $v 1] " l=[llength $pq]"
165     } else {
166         debug "cdu-charged"
167         unset pq
168     }
169 }
170
171 proc randbyte {} {
172     global rand
173     set c [read $rand 1]; if {![string length $c]} { error "eof on rand" }
174     binary scan $c H* x
175     return $x
176 }
177
178 proc pt_maybe {point oneisright} {
179     global pointasked lastptchosen
180     if {![info exists pointasked]} {
181         if {![string compare $point $lastptchosen]} return
182         set lastptchosen $point
183         set x [randbyte]
184         set pos [expr [regexp {^[89a-f]} $x] ? 1 : 0]
185         debug "chose point $point pos=$pos (x=$x)"
186     } elseif {[regexp {^M([01])$} $pointasked dummy pos]} {
187         if {[lsearch -exact {40 02} [lindex $point 0]] >= 0} {
188             set pos [expr {!$pos}]
189             debug "chose point $point pos=$pos manual-rl"
190         } else {
191             debug "chose point $point pos=$pos manual-lr"
192         }
193         set pointasked M
194     } elseif {![string compare $pointasked M]} {
195         debug "leave point $point manual"
196         return
197     } else {
198         set pos $pointasked
199         debug "fixed point $point pos=$pos"
200     }
201     pt_must $point $pos
202 }
203
204 proc s0 {v seg} {
205     upvar #0 segdetect($seg) segd
206     if {![info exists segd]} {
207         debug "segment $seg = already"
208     } elseif {[string length $segd]} {
209         debug "segment $seg = pending already"
210     } else {
211         debug "segment $seg = soon"
212         set segd [after 100 s0t $seg]
213     }
214 }
215 proc s0t {seg} {
216     upvar #0 segdetect($seg) segd
217     debug "segment $seg = now"
218     unset segd
219     gui "D0 $seg"
220 }
221 proc s1 {v seg} {
222     upvar #0 segdetect($seg) segd
223     if {![info exists segd]} {
224         pm_detect $v
225         debug "segment $seg ! (overwrites =)"
226     } elseif {[string length $segd]} {
227         debug "segment $seg ! (cancels =)"
228         after cancel $segd
229     } else {
230         debug "segment $seg ! already"
231         return
232     }
233     gui "D1 $seg"
234     set segd {}
235 }       
236
237 proc pm_maydetect {d seg} {
238     switch -exact $seg {
239         06 { s$d $seg X10 }
240         09 { s$d $seg X8 }
241         0a { s$d $seg X6 }
242         04 { s$d $seg X5 }
243         02 { s$d $seg X7 }
244         07 { s$d $seg X9 }
245         14 { s$d $seg A5 }
246         20 { s$d $seg A6 }
247         1a { s$d $seg A4 }
248         10 { s$d $seg A2 }
249         03 { s$d $seg X1 }
250         05 { s$d $seg X3 }
251         16 { s$d $seg A3 }
252         1c { s$d $seg A1 }
253         08 { s$d $seg X2 }
254         0b { s$d $seg X4 }
255     }
256 }
257
258 #proc pm_nodetect {seg} {
259 #    global segsasgot
260 #    if {![string compare $seg [lindex $segsasgot 1]]} {
261 #       set segsasgot [list [lindex $segsasgot 1] [lindex $segsasgot 0]]
262 #    }
263 #}
264
265 proc pm_detect {seg} {
266     global segs pname segsasgot
267     if {[string compare $seg [lindex $segsasgot 1]]} {
268         set segsasgot [list [lindex $segsasgot 1] $seg]
269     }
270     if {[lsearch -exact $segs $seg] < 0} {
271         set segs $segsasgot
272     }
273     debug "pm_detect $seg ($segsasgot) ($segs) $pname$seg"
274 #    if {[lsearch -exact {
275 #       06 09 0a 04 02 07 14 20
276 #       0b 08 1c 16
277 #       1a 10 03 05
278 #    } $seg] < 0} return
279     switch -exact $pname$seg {
280         l16 - l1c - l08 - l0b { polarity y }
281         l10 - l1a - l03 - l05 { polarity x }
282         x07 - x04 - x0a { polarity l }
283         x16 - x1c - x14 - x0b { polarity y }
284         y06 - y04 - y0a { polarity l }
285         y20 - y10 - y1a - y05 { polarity x }
286     }
287     switch -exact $seg {
288         04 - 0a { pt_must "00 01 X7" 1; pt_must "40 41 X8" 1 }
289         05 { pt_must "00 01 X7" 0 }
290         0b { pt_must "40 41 X8" 0 }
291         16 - 1c { pt_must "02 03 A5" 0 }
292         1a - 10 { pt_must "42 43 A6" 0 }
293         14 { pt_ifthenmust "02 03 A5" 1 "42 43 A6" 1 }
294         20 { pt_ifthenmust "42 43 A6" 1 "02 03 A5" 1 }
295     }
296     switch -exact [join $segs -] {
297         02-07 { pt_maybe "02 03 A5" 1 }
298         07-02 { pt_maybe "00 01 X7" 0 }
299         09-06 { pt_maybe "42 43 A6" 0 }
300         06-09 { pt_maybe "40 41 X8" 1 }
301     }
302 }
303
304 proc tellnmra {m} {
305 #    global nmrawhich speeddirn funcs
306 #    set m 0x$m
307 #    for {set i 0} {$i < $m} {incr i} {
308 #       tellpic_q [lindex [list $speeddirn $funcs] $nmrawhich]
309 #       set nmrawhich [expr {!$nmrawhich}]
310 #    }
311 }
312
313 proc watchdog {} {
314     global watchdog testonly speeddirn funcs nmradiv
315     catch { after cancel $watchdog }
316     set watchdog [after 50 watchdog]
317     tellpic_q 9808 ;# 128ms
318     if {[incr nmradiv] > 35} {
319         tellpic_q $speeddirn$funcs
320         set nmradiv 0
321     }
322 }
323
324 proc pm_hello {} {
325     debug "got hello, starting up"
326     tellpic 11
327     gui "P 1"
328     watchdog
329     changewhat
330     tellnmra 01
331 }
332
333 proc fp {m} {
334     debug "<< $m"
335 }
336
337 proc frompic {m} {
338     set v [lindex $m 1]
339     switch -glob [lindex $m 0] {
340         01 - 02 { tellnmra $m }
341         09 { fp $m; pm_hello }
342         07 { puts "short circuit"; exit 1 }
343         0d { fp $m; badwatchdog }
344         28 { fp $m; pm_charged }
345         9[0-7] { fp $m; pm_maydetect 0 $v }
346         9? { fp $m; pm_maydetect 1 $v }
347         0a - [234567]? { puts "pic debug $m" }
348         * { fp $m; fail "pic unknown $m" }
349     }
350 }
351
352 proc onreadp_test {} {
353     if {![gets stdin m]} { return }
354     frompic $m
355 }
356
357 proc onreadp {} {
358     global p m rand
359     while 1 {
360         set c [read $p 1]
361         if {![string length $c]} {
362             if {[eof $p]} { error "eof on device" }
363             return
364         }
365         binary scan $c H* x
366         if {![info exists rand]} {
367             fp ...$x
368             return
369         }
370         lappend m $x
371         if {[regexp {^[0-7]} $x]} {
372             if {![regexp {^x} $m]} {
373                 frompic $m
374             }
375             set m {}
376         }
377     }
378 }
379
380 proc newspeeddirn {} {
381     global loco
382     set b1 0x[randbyte]
383     set speed [expr {round(($b1 * $b1) / 65535.0 * 100.0 + 26.0)}]
384     set b2 0x[randbyte]
385     set dirn [expr {$b2 / 128}]
386     set dirn 0
387     debug "speeddirn b1=$b1 speed=$speed b2=$b2 dirn=$dirn"
388     return "speed126 $loco $speed $dirn"
389 }
390
391 proc newfuncs {} {
392     global loco
393     set b3 0x[randbyte]
394     set value [expr {($b3 & 127) * 16}]
395     debug "funcs b3=$b3 value=[format %x $value]"
396     return "funcs5to8 $loco $value"
397 }
398
399 proc maybechange {thing force} {
400     global $thing ch
401     upvar #0 ${thing}_fixed fixed
402     if {![info exists fixed]} {
403         if {$force} {
404             debug "maybechange $thing forced ..."
405         } else {
406             set rb 0x[randbyte][randbyte]
407             if {
408                 $rb / 65536.0 >
409                 1.0 / (($ch(${thing}every) - $ch(minint)*0.001) * $ch(scale))
410             } {
411                 debug "maybechange $thing rb=$rb no"
412                 return 0
413             }
414             debug "maybechange $thing rb=$rb yes ..."
415         }
416         set l [new$thing]
417     } else {
418         debug "fixed $thing $fixed"
419         set l $fixed
420         if {![llength $l]} { return 0 }
421     }
422     set bin [eval exec ./hostside-old -s/dev/stdout $l]
423     binary scan $bin H* x
424     debug "changed $thing=$x"
425     set $thing ff$x
426     return 1
427 }
428
429 proc changewhat {} {
430     global ch chwa
431     catch { after cancel $chwa }
432     if {[maybechange speeddirn 0] || [maybechange funcs 0]} {
433         set interval $ch(minint)
434     } else {
435         set interval 1000
436     }
437     set chwa [after $interval changewhat]
438 }
439
440 proc onreadcmd {} {
441     if {[gets stdin l] < 0} {
442         if {[eof stdin]} {
443             puts "GUI exit 0"
444             fail "stopgap-controller got eof, quitting"
445             fileevent stdin readable {}
446         }
447         return
448     }
449     eval $l
450 }
451
452 proc setup {} {
453     global port p testonly
454     fconfigure stdout -buffering none
455     if {!$testonly} {
456         set p [open $port {RDWR NONBLOCK} 0]
457     
458         exec stty -F $port min 1 time 0 -istrip -ocrnl -onlcr -onocr -opost \
459                        -ctlecho -echo -echoe -echok -echonl -iexten -isig \
460                        -icanon -icrnl \
461             9600 clocal cread crtscts -hup -parenb cs8 -cstopb \
462             -ixoff bs0 cr0 ff0 nl0 -ofill -olcuc
463
464         fconfigure $p -encoding binary -translation binary \
465                 -blocking false -buffering none
466
467         fileevent $p readable onreadp
468         fconfigure stdin -blocking false
469         fileevent stdin readable onreadcmd
470     } else {
471         set p stdin
472         fconfigure stdin -blocking false
473         fileevent stdin readable onreadp_test
474     }
475
476     after 250 setup_complete
477 }
478
479 proc setup_complete {} {
480     global rand
481     set rand [open /dev/urandom {RDONLY} 0]
482     fconfigure $rand -encoding binary -translation binary
483     tellpic 0a
484 }
485
486
487 #----------
488 # for keyboard control
489
490 proc ask_speed {updown} {
491     global speeddirn_fixed askspeeds askspeedix loco
492     set ll [llength $askspeeds]
493     if {![info exists askspeedix]} {
494         set askspeedix [expr {
495             int($ll * 0.5 - 0.5 + 0.5 * $updown)
496         }]
497     } else {
498         incr askspeedix $updown
499         if {$askspeedix < 0} { set askspeedix 0 }
500         if {$askspeedix >= $ll} { set askspeedix [expr {$ll - 1}] }
501     }
502     set speed [lindex $askspeeds $askspeedix]
503     set speeddirn_fixed [list speed126 $loco $speed 0]
504     maybechange speeddirn 1
505 }
506
507 proc ask_randspeed {} {
508     global speeddirn_fixed askspeedix
509     catch { unset speeddirn_fixed }
510     catch { unset askspeedix }
511     maybechange speeddirn 1
512 }    
513
514 proc ask_manual {rightp} { global pointasked; set pointasked M$rightp }
515 proc ask_figureeight {} { global pointasked; set pointasked 0 }
516 proc ask_loop {} { global pointasked; set pointasked 1 }
517 proc ask_randpath {} { global pointasked; catch { unset pointasked } }
518
519 setup
520 gui_init
521 vwait end