chiark / gitweb /
wip multiplexer
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 19 Jan 2012 00:54:16 +0000 (00:54 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 19 Jan 2012 00:54:16 +0000 (00:54 +0000)
ttyacm-multiplex [new file with mode: 0755]

diff --git a/ttyacm-multiplex b/ttyacm-multiplex
new file mode 100755 (executable)
index 0000000..727edf1
--- /dev/null
@@ -0,0 +1,245 @@
+#!/usr/bin/tclsh8.4
+# -*- tcl -*-
+
+proc debug {m} { puts "DEBUG $m" }
+
+proc log {m} { puts "LOG $m" }
+
+proc experror {m} {
+    error $m {} EXPECTED
+}
+
+proc find-devices {} {
+    global errorCode errorInfo devices
+    set base /sys/class/tty
+    foreach candidate [glob -nocomplain -directory $base -tails ttyACM*] {
+       debug "candidate $candidate"
+       if {[catch { file link $base/$candidate/device } ltarget]} {
+           debug " readlink failed [lrange $errorCode 0 1]"
+           switch -glob $errorCode {
+               {POSIX EINVAL *} continue
+               {POSIX ENOENT *} continue
+               default { error "$ltarget \[$errorCode] $errorInfo $errorCode" }
+           }
+       }
+       if {![regexp {^(.*)\.(\d+)$} $ltarget dummy dbase interf]} {
+           debug " readlink bad target $ltarget"
+           continue
+       }
+       debug " approved $dbase $interf $candidate"
+       lappend devs($dbase) [list $interf $candidate]
+    }
+    set howmany [array size devs]
+    if {!$howmany} {
+       experror "no appropriate device(s) found"
+    }
+    if {$howmany > 1} {
+       experror "several appropriate device(s) found [array names $devs]"
+    }
+    set devices {}
+    foreach dev [lsort -index 0 -integer $devs([lindex [array names devs] 0])] {
+       lappend devices [lindex $dev 1]
+    }
+}
+
+proc reopen-our-device {} {
+    global devices dchan
+    find-devices
+    set dchan [open /dev/[lindex $devices 1] r+]
+    fconfigure $dchan -blocking no -buffering line -translation {crlf cr}
+    read $dchan; # flush input
+    puts $dchan ATE0
+    flush $dchan
+    after 250
+    set result [read $dchan]
+    if {![regexp -line {^OK$} $result]} { experror "got [logquote $result]" }
+    fileevent $dchan readable dchan-readable
+}
+
+proc devfailure {emsg} {
+    global errorCode errorInfo dchan
+    switch -glob $errorCode {
+       {POSIX *} - EXPECTED {
+           log "device failure: $emsg"
+       }
+       default {
+           log "unexpected device failure: $emsg"
+           foreach l [split $errorInfo "\n"] {
+               log "  $l"
+           }
+       }
+    }
+    if {[info exists dchan]} {
+       catch { close $dchan }
+       catch { unset dchan }
+    }
+}
+
+proc try-open-our-device {} {
+    global devices
+    if {[catch {
+       reopen-our-device
+    } emsg]} {
+       devfailure $emsg
+       return
+    }
+    sendout-async *TTYATMUX "*TTYATMUXDEVS [join $devices ,]"
+    sendout-async *TTYATMUX "*TTYATMUXOPEN"
+}
+
+proc dchan-readable {args} {
+    global dchan
+    while 1 {
+       if {[catch {
+           gets $dchan l
+       } r]} {
+           devfailure $r
+           return
+       }
+       if {$r<0} {
+           if {![eof $dchan]} return
+           set errorCode EXPECTED
+           devfailure "eof"
+           return
+       }
+       dchan-line $l
+    }
+}
+
+proc async-notif-or-resp-varies {async_values asid l} {
+    set ll [llength [split $l ,]]
+    if {$ll == $async_values} {
+       async-notif $asid $l
+    } elseif {$ll == $async_values+1} {
+       sync-reply $l
+    } else {
+       bad-data $l "async-notif-or-resp-varies $ll"
+    }
+}
+
+proc async-notif-or-resp-fixed {asid l} {
+    global current_command_asid
+    if {![string compare $asid $current_command_asid]} {
+       sync-reply $l
+    } else {
+       async-notif $asid $l
+    }
+}
+
+proc async-notif-creg {asid l} {
+    set ll [llength [split $l ,]]
+    switch -exact $ll {
+       4 { sync-reply $l }
+       3 { 
+           async-notif {+CREG} 2 2 $l
+           async-notif {+CREG} 1 1 [lindex [split $l ,] 0]
+       }
+       2 { sync-reply $l }
+       1 { 
+           async-notif {+CREG} 1 2 $l
+       }
+       default {
+           bad-data $l "async-notif-creg $ll"
+       }
+    }
+}
+
+proc async-control-max0 {c l allows} {
+    async-control-core $c $l $allows {
+       set wanted 0
+    } {
+       set tw 0
+       manyset $ca($c) tw
+       if {$tw} { set wanted 1 }
+    } {
+       set send $wanted
+       foreach allow $allows { lappend $send [lindex $allow 0] }
+       sync-subcommand $c "$cmd=[join $send ,]" async-updated-ok $c
+    }
+}
+
+proc async-control-cmer {c l allows} {
+    async-control-core $c $l $allows {
+       set send 0,0,0,0
+    } {
+       set mode 0; set ind 0
+       manyset $ca($c) mode keyp disp ind bfr
+       if {$mode==3 && $ind} { set send 3,0,0,1 }
+    } {
+       sync-subcommand $c "$cmd=$send" async-updated-ok $c
+    }
+}
+
+proc async-updated-ok
+
+proc async-control-core {c l allows ubody_init ubody_perclient ubody_finish} {
+    global clients
+    uplevel 1 cmd cmd
+    if {[regexp {^(AT[^=?])\?$} dummy cmd]} {
+       sync-subcommand $c $cmd async-massage-result-subs $c $cmd
+    } elseif {[regexp {^(AT[^=?])=\?$} dummy cmd]} {
+       sync-subcommand $c $cmd async-massage-result-support $c $cmd $allows
+    } elseif {[regexp {^(AT[^=?])=([0-9,]+)$} dummy cmd values]} {
+       set values [split $values ,]
+       if {[llength $values] > [llength $allows]} {
+           bad-command "too many values"
+           return
+       }
+       while {[llength $values] < [llength $allows]} {
+           lappend values 0
+       }
+       foreach val $values allow $allows {
+           if {[lsearch -exact $allow $val]<0} {
+               bad-command "$val not in allowed $allow ($allows)"
+               return
+           }
+       }
+       uplevel 1 [list upvar #0 client_async/$cmd ca]
+       upvar #0 client_async/$cmd ca
+       set ca($c) $values
+       uplevel 1 $ubody_init
+       upvar 1 c uc
+       foreach uc [array names clients] {
+           uplevel 1 $ubody_perclient
+       }
+       uplevel 1 $ubody_finish
+    } else {
+       bad-command "unknown async control syntax"
+    }
+}
+
+proc set-client-echo {c yn} {
+    global client_echo
+    set client_echo($c) 0
+    client-command-complete $c OK
+}
+
+proc process-client-command {c nl} {
+    switch -regexp $l {
+       {^AT\+CREG\b} { async-control-max0 $c $l {{0 1 2}} }
+       {^AT\+CGREG\b} { async-control-max0 $c $l {{0 1 2}} }
+       {^AT\*ERINFO\b} { async-control-max0 $c $l {{0 1}} }
+       {^AT\+CGEREP\b} { async-control-max0 $c $l {{0 1 2} 0} }
+       {^AT\+CMER\b} { async-control-cmer $c $l {{0 3} 0 0 {0 1} 0} }
+       {^ATE0$} { set-client-echo $c 0 }
+       {^ATE1$} { set-client-echo $c 1 }
+    
+
+proc dchan-line {l} {
+    global cclient
+    switch -regexp $l {
+       {\+CREG:}   { async-notif-creg             +CREG   $l }
+       {\+CGREG:}  { async-notif-creg             +CGREG      $l }
+       {\*ERINFO:} { async-notif-or-resp-fixed    *ERINFO $l }
+       {\+CGEV:}   { async-notif                  +CGEREP   $l }
+
+       {\+CIEV:}   { async-notif                  +CIEV   $l }
+    }
+
+    if {[info exists cclient
+
+proc sendout-async
+proc logquote
+
+
+try-open-our-device