4 # Copyright 2016,2020 Ian Jackson
5 # SPDX-License-Identifier: GPL-3.0-or-later
6 # There is NO WARRANTY.
8 # This is unfinished attempt at a program to multiplex multiple
9 # clients all wanting to speak to a /dev/ttyACM GSM modem / GPS.
13 proc debug {m} { puts "DEBUG $m" }
15 proc log {m} { puts "LOG $m" }
21 proc find-devices {} {
22 global errorCode errorInfo devices expected_devs
23 set base /sys/class/tty
24 foreach candidate [glob -nocomplain -directory $base -tails ttyACM*] {
25 debug "candidate $candidate"
26 if {[catch { file link $base/$candidate/device } ltarget]} {
27 debug " readlink failed [lrange $errorCode 0 1]"
28 switch -glob $errorCode {
29 {POSIX EINVAL *} continue
30 {POSIX ENOENT *} continue
31 default { error "$ltarget \[$errorCode] $errorInfo $errorCode" }
34 if {![regexp {^(.*)\.(\d+)$} $ltarget dummy dbase interf]} {
35 debug " readlink bad target $ltarget"
38 debug " approved $dbase $interf $candidate"
39 lappend devs($dbase) [list $interf $candidate]
41 set howmany [array size devs]
43 experror "no appropriate device(s) found"
46 experror "several appropriate device(s) found [array names $devs]"
49 foreach dev [lsort -index 0 -integer $devs([lindex [array names devs] 0])] {
50 lappend devices [lindex $dev 1]
52 if {[llength $devices] != $expected_devs} {
53 experror "wrong # devices ($devices), expected $expected"
57 proc create-dev-nodes {} {
58 global devices expected_devs
59 set ourdevs /dev/atmux
61 for {set i 0} {$i < $expected_devs-1} {incr i} {
63 file link -symbolic ../$device $ourdevs/.new.$new
64 file rename -force $ourdevs/.new.$new $ourdevs/$new
67 foreach candidate [glob -nocomplain -directory $ourdevs -tails ttyAT*] {
68 if {![info exists wanted($candidate)]} {
69 file remove $ourdevs/$candidate
74 proc reopen-our-device {} {
76 set dchan [open /dev/[lindex $devices 1] r+]
77 fconfigure $dchan -blocking no -buffering line -translation {crlf cr}
78 read $dchan; # flush input
82 set result [read $dchan]
83 if {![regexp -line {^OK$} $result]} { experror "got [logquote $result]" }
84 fileevent $dchan readable chan-readable $dchan dchan "modem device"
93 proc dchan-failure {dummy} {
95 if {[info exists dchan]} {
96 catch { close $dchan }
101 proc try-open-our-device {} {
109 sendout-async *TTYATMUX "*TTYATMUXDEVS [join $devices ,]"
110 sendout-async *TTYATMUX "*TTYATMUXOPEN"
113 proc trap-log {what body var} {
114 global errorCode errorInfo
121 switch -glob $errorCode {
122 {POSIX *} - EXPECTED { log "$what: $result" }
124 log "unexpected: $what: $result"
125 foreach l [split $errorInfo "\n"] { log " $l" }
134 return -code $rc -errorinfo $errorInfo \
135 -errorcode $errorCode $result
140 proc chan-readable {chan how what args} {
142 if {[trap-log "$what failure" {
149 if {![eof $chan]} return
154 trap-log "error processing $what data" {
160 proc async-notif fixme this has wrong arguments
162 proc async-notif-or-resp-fixed {asid l} {
163 global current_command_asid
164 if {![string compare $asid $current_command_asid]} {
171 proc async-notif-creg {asid l} {
172 set ll [llength [split $l ,]]
176 async-notif {+CREG} 2 2 $l
177 async-notif {+CREG} 1 1 [lindex [split $l ,] 0]
181 async-notif {+CREG} 1 2 $l
184 bad-data $l "async-notif-creg $ll"
189 proc async-control-max0 {c l allows} {
190 async-control-core $c $l $allows {
195 if {$tw} { set wanted 1 }
198 foreach allow $allows { lappend $send [lindex $allow 0] }
199 sync-subcommand $c "$cmd=[join $send ,]" async-updated-ok $c
203 proc async-control-cmer {c l allows} {
204 async-control-core $c $l $allows {
207 set mode 0; set ind 0
208 manyset $ca($c) mode keyp disp ind bfr
209 if {$mode==3 && $ind} { set send 3,0,0,1 }
211 sync-subcommand $c "$cmd=$send" async-updated-ok $c
215 proc async-updated-ok
217 proc async-control-core {c l allows ubody_init ubody_perclient ubody_finish} {
220 if {[regexp {^(AT[^=?])\?$} $l dummy cmd]} {
221 sync-subcommand $c $l async-massage-result-subs $c $cmd
222 } elseif {[regexp {^(AT[^=?])=\?$} $l dummy cmd]} {
223 sync-subcommand $c $l async-massage-result-support $c $cmd $allows
224 } elseif {[regexp {^(AT[^=?])=([0-9,]+)$} $l dummy cmd values]} {
225 set values [split $values ,]
226 if {[llength $values] > [llength $allows]} {
227 bad-command "too many values"
230 while {[llength $values] < [llength $allows]} {
233 foreach val $values allow $allows {
234 if {[lsearch -exact $allow $val]<0} {
235 bad-command "$val not in allowed $allow ($allows)"
239 uplevel 1 [list upvar #0 client_async/$cmd ca]
240 upvar #0 client_async/$cmd ca
242 uplevel 1 $ubody_init
244 foreach uc [array names clients] {
245 uplevel 1 $ubody_perclient
247 uplevel 1 $ubody_finish
249 bad-command "unknown async control syntax"
253 proc set-client-echo {c yn} {
255 set client_echo($c) 0
256 client-command-complete $c OK
259 proc simple-command {c l} {
260 sync-subcommand $c $l simple-command-complete
263 proc client-command-complete
265 proc process-client-command {c nl} {
267 {^AT\+CREG\b} { async-control-max0 $c $l {{0 1 2}} }
268 {^AT\+CGREG\b} { async-control-max0 $c $l {{0 1 2}} }
269 {^AT\*ERINFO\b} { async-control-max0 $c $l {{0 1}} }
270 {^AT\+CGEREP\b} { async-control-max0 $c $l {{0 1 2} 0} }
271 {^AT\+CMER\b} { async-control-cmer $c $l {{0 3} 0 0 {0 1} 0} }
272 {^ATE0$} { set-client-echo $c 0 }
273 {^ATE1$} { set-client-echo $c 1 }
274 {^AT\+CFUN\b} { simple-command $c $l }
275 default { bad-command "unknown command" }
279 proc dchan-line {l} {
282 {\+CREG:} { async-notif-creg +CREG $l }
283 {\+CGREG:} { async-notif-creg +CGREG $l }
284 {\*ERINFO:} { async-notif-or-resp-fixed *ERINFO $l }
285 {\+CGEV:} { async-notif +CGEREP $l }
286 {\+CIEV:} { async-notif +CMER $l }
287 default { syync-reply $l }
291 proc cchan-line {c l} {
292 lappend queue [list $c $l]
296 proc sync-subcommand {c l args} {
298 if {[info exists busy]} { error "already busy $busy; want $c $l $args" }
299 if {[trap-log "write device" { puts $dchan $l } dummy]} {
302 proc sync-reply {l} {
304 if {![info exists busy]} {
305 bad-data $l "unexpected sync reply"
312 while {![info exists busy] && [llength $queue]} {
313 manyset [lindex $queue 0] c l
314 set queue [lrange $queue 1 end]
315 if {[trap-log "process for $c [logquote $l]"] {
316 process-client-command $c $l
318 client-command-complete $c ERROR
323 proc client-command-complete {c l} {
324 if {[trap-log "write to $c" { puts $c $l } dummy]} {
329 proc cchan-readable {