aboutsummaryrefslogtreecommitdiff
path: root/examples/tip.tcl
blob: fc6990cd7aea6f79006f6f984f257cc329e34e68 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/env jimsh

# tip.tcl is like a simple version of cu, written in pure Jim Tcl
# It makes use of the new aio tty support

# Note: On Mac OS X, be sure to open /dev/cu.* devices, not /dev/tty.* devices

set USAGE \
{Usage: tip ?settings? device
    or tip help

Where settings are as follows:
1|2             stop bits   (default 1)
5|6|7|8         data bits   (default 8)
even|odd        parity      (default none)
xonxoff|rtscts  handshaking (default none)
<number>        baud rate   (default 115200)

e.g. tip 9600 8 1 rtscts /dev/ttyUSB0}

set settings {
	baud 115200
	stop 1
	data 8
	parity none
	handshake none
	input raw
	output raw
	vmin 1
	vtime 1
}

set showhelp 0

foreach i $argv {
	if {[string match -h* $i] || [string match help* $i]} {
		puts $USAGE
		return 0
	}
	if {$i in {even odd}} {
		set settings(parity) $i
		continue
	}
	if {$i in {ixonixoff rtscts}} {
		set settings(handshake) $i
		continue
	}
	if {$i in {1 2}} {
		set settings(stop) $i
		continue
	}
	if {$i in {5 6 7 8}} {
		set settings(data) $i
		continue
	}
	if {[string is integer -strict $i]} {
		set settings(baud) $i
		continue
	}
	if {[file exists $i]} {
		set device $i
		continue
	}
	puts "Warning: unrecognised setting $i"
}

if {![exists device]} {
	puts $USAGE
	exit 1
}

# save stdin and stdout tty settings
# note that stdin and stdout are probably the same file descriptor,
# but it doesn't hurt to treat them independently
set stdin_save [stdin tty]
set stdout_save [stdout tty]

try {
	set f [open $device r+]
} on error msg {
	puts "Failed to open $device"
	return 1
}

if {[$f lock] == 0} {
	puts "Device is in use: $device"
	return 1
}

try {
	$f tty {*}$settings
} on error msg {
	puts "$device: $msg"
	return 1
}

puts "\[$device\] Use ~. to exit"

$f ndelay 1
$f buffering none

stdin tty input raw
stdin ndelay 1

stdout tty output raw
stdout buffering none

set status ""
set tilde 0

proc bgerror {args} {
	set status $args
	incr ::done
}

# I/O loop

$f readable {
	set c [$f read]
	#stdout puts "<serial:$c([string bytelength $c]>"
	#stdout flush
	if {[$f eof]} {
		set status "$device: disconnected"
		incr done
	}
	stdout puts -nonewline $c
}

proc tilde_timeout {} {
	global tilde f
	if {$tilde} {
		$f puts -nonewline ~
		set tilde 0
	}
}

stdin readable {
	set c [stdin read]
	# may receive more than one char here, but only need to consider
	# ~. processing if we receive them as separate chars
	if {$tilde == 0 && $c eq "~"} {
		incr tilde
		# Need ~. within 1 second of each other
		after 1000 tilde_timeout
	} else {
		if {$tilde} {
			after cancel tilde_timeout
			set tilde 0
			if {$c eq "."} {
				incr done
				return
			}
			$f puts -nonewline ~
		}
		$f puts -nonewline $c
	}
}

vwait done

# restore previous settings
stdin tty {*}$stdin_save
stdout tty {*}$stdout_save

puts $status