JMeh 13 Jul 2017 - SerWatch (Serial Watcher)
SerWatch is a little Tcl library for analyzing protocols over a serial line.
I often have to connect serial devices like scales, PD controller or several testing machines and therefore I wrote this library. You have to make a serial adapter which has to be connected with both communication partners. Both serial data lines (TxD and RxD) must be connected to each of the RxD line of two additional serial adapters in your PC. I use USB virtual COM port adapters for my Mac. Here is a little schematic:
DB-25(f) DB-25(m)
2 -----*---------------- 2 TxD
3 -----|----------*----- 3 RxD
4 -----|----------|----- 4 RTS
5 -----|----------|----- 5 CTS
6 -----|----------|----- 6 DSR
7 -----|--*----*--|----- 7 GND
8 -----|--|----|--|----- 8 DCD
20 -----|--|----|--|----- 20 DTR
| | | |
| | | |
| | | |
DB-9(f) | | | | DB-9(f)
2 -----+ | | +----- 2 RxD
5 --------+ +-------- 5 GND
7 --\ /-- 7 RTS
8 --/ \-- 8 CTS
1 --\ /-- 1 DCD
4 --+ +-- 4 DTR
6 --/ \-- 6 DSR
Then you can write a little Tcl script to use SerWatch like this:
package require Serwatch
Serwatch::Init -tty1 /dev/cu.usbserial-FTGZMSMJ -tty2 /dev/cu.usbserial-FTGZMTOX -hex no \
-baud 9600,e,8,2 -win . -log impact450.log -tout 5000
I think, the parameters are self explained.
This shows in the main window what's going on, if the serial mode parameter is correct :-) and also writes all lines to the given log file.
Here is an example of a typical log:
09:59:02.000 INIT Serwatch
09:59:02.016 tty1 = /dev/ttyUSB0
09:59:02.016 tty2 = /dev/ttyUSB1
09:59:02.016 baud = 9600,e,7,1
09:59:02.031 tty1 OK (file5 = <-) -- reading input
09:59:02.043 tty2 OK (file6 = ->) -- reading input
10:03:30.648 <- ␂ U M ; T I P U 1 5 1 5 3 9 1 ␣ ␣ ␣ ␣ ; ␃ ␊ ␍
10:03:31.669 -> ␂ 0 0 A 0 0 0 1 ␃ ␊ ␍
10:03:31.880 <- ␊ ␍
10:03:36.885 timeout
10:03:44.262 <- ␂ U M ; B 0 0 0 1 ␣ ␣ ␣ ␣ ␣ ; ␃ ␊ ␍ ␣
10:03:45.298 -> ␂ 0 0 ␃ ␊ ␍
10:03:45.477 <- ␊ ␍
10:03:50.478 timeout
10:05:15.591 <- ␂ C B ; T I P U 1 5 1 5 3 9 1 ␣ ␣ ␣ ␣ ; ␃ ␊ ␍
10:05:16.628 -> ␂ 0 0 B 0 0 0 1 ␃ ␊ ␍
10:05:16.823 <- ␊ ␍
10:05:21.824 timeout
10:05:26.214 <- ␂ C B ; B 0 0 0 1 ␣ ␣ ␣ ␣ ␣ ␣ ; ␃ ␊ ␍
10:05:27.234 -> ␂ 0 0 ␃ ␊ ␍
10:05:27.413 <- ␊ ␍
10:05:32.417 timeout
10:05:41.011 <- ␂ C B ; 2 4 1 1 1 1 1 1 ; ␃ ␊ ␍
10:05:42.032 -> ␂ 0 0 ␃ ␊ ␍
10:05:42.211 <- ␊ ␍
10:05:47.214 timeout
10:05:54.513 <- ␂ C B ; S E E D ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ; ␃
10:05:55.550 -> ␂ 0 0 ␃ ␊ ␍
10:05:55.713 <- ␊ ␍
10:06:00.719 timeout
10:06:04.143 <- ␂ C B ; B ␣ ␣ ; ␃ ␊ ␍
10:06:05.180 -> ␂ 0 0 ␃ ␊ ␍
10:06:05.343 <- ␊ ␍
10:06:10.344 timeout
The library is tested on macos X, Linux and Windows.
And here is the source:
#
# Serial Watcher (serwatch)
# =========================
#
# Beobachtung zweier serieller Schnittstellen zur Analyse des Datenverkehrs
# zwischen zwei Geräten. Dazu muß ein doppel-T Kabel angefertigt werden:
#
# DB-25(f) DB25(m)
# 2 -----*---------------- 2 TxD
# 3 -----|----------*----- 3 RxD
# 4 -----|----------|----- 4 RTS
# 5 -----|----------|----- 5 CTS
# 6 -----|----------|----- 6 DSR
# 7 -----|--*----*--|----- 7 GND
# 8 -----|--|----|--|----- 8 DCD
# 20 -----|--|----|--|----- 20 DTR
# | | | |
# | | | |
# | | | |
# DB-9(f) | | | | DB-9(f)
# 2 -----+ | | +----- 2 RxD
# 5 --------+ +-------- 5 GND
# 7 --\ /-- 7 RTS
# 8 --/ \-- 8 CTS
# 1 --\ /-- 1 DCD
# 4 --+ +-- 4 DTR
# 6 --/ \-- 6 DSR
#
# Die beiden DB-25 sind 1:1 mit einander verbunden. Die beiden DB-9 Buchsen
# sind lediglich mit der Empfangsleitung und Masse mit den DB-25 verbunden.
# Vorsichtshalber sind die Handshake-Leitungen in den DB-9 Buchsen gebrückt
# (7-8 und 1-4-6).
#
namespace eval Serwatch {
variable config
array set config "
tty1 /dev/ttyS0
tty2 /dev/ttyS1
baud 9600,n,8,1
fd1 {}
fd2 {}
key {}
win {}
txw {}
sbw {}
timer {}
time0 {}
tout 1000
olen 0
lcnt 0
log {}
fdlog {}
hex 0
uchar yes
font Monaco
fsize 13
ascnm {NUL SOH STX ETX EOT ENQ ACK BEL BS TAB LF VT FF CR SO SI
DLE XON DC2 XOF DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US}
"
namespace export Init
}
proc Serwatch::Hex { c } {
variable config
binary scan $c c asc
if {$config(hex)} {
return [format %02X [expr {$asc & 0xFF}]]
} else {
if {$asc < 32} {
if {$config(uchar)} {
return [subst -nocommands -novariables "\\u24[format %02X $asc]"]
} else {
return [lindex $config(ascnm) $asc]
}
} elseif {$asc == 32} {
if {$config(uchar)} {
return "\u2423"
} else {
return ._.
}
} elseif {$asc > 126} {
return [format 0x%02X $asc]
} elseif {$asc == 127} {
if {$config(uchar)} {
return "\u2421"
} else {
return DEL
}
}
}
return $c
}
proc Serwatch::Read { fd key } {
variable config
after cancel $config(timer)
if {[eof $config($fd)]} {
Serwatch::Close
return
}
if {[set c [read $config($fd) 1]] != ""} {
if {$config(tout) != {}} {
set config(timer) [after $config(tout) Serwatch::Timeout]
}
Message $key [Hex $c]
}
}
proc Serwatch::Timeout {} {
Message "timeout"
}
proc Serwatch::Close {} {
variable config
catch { close $config(fd1) }
catch { close $config(fd2) }
set config(fd1) {}
set config(fd2) {}
after 1000 Serwatch::Init
}
proc Serwatch::Init { args } {
variable config
if {$args != {}} {
set argn [llength $args]
for {set argi 0} {$argi < $argn} {incr argi} {
set arg [lindex $args $argi]
switch -- $arg {
-tty1 - -tty2 - -baud - -win - -tout - -log - -hex {
set opt [string range $arg 1 end]
set config($opt) [lindex $args [incr argi]]
}
default {
set opts "-tty1, -tty2, -baud, -win, -tout, -log, or -hex"
error "bad option \"$arg\": must be $opts"
}
}
}
}
if {$config(log) != {}} {
catch { open $config(log) a } config(fdlog)
}
set t0 [clock seconds]
while 1 {
set usec [clock clicks -milliseconds]; set sec [clock seconds]
if {$sec != $t0} {
set config(time0) [expr {$usec % 1000}]
break
}
}
Message INIT Serwatch
Message tty1 "= $config(tty1)"
Message tty2 "= $config(tty2)"
Message baud "= $config(baud)"
set ok 1
foreach {tty fd key} {tty1 fd1 <- tty2 fd2 ->} {
if {$config($tty) != "none"} {
if {[regexp {^tcp:([\w\.]+):(\w+)$} $config($tty) all host port]} {
set rc [catch { set config($fd) [socket $host $port] } err]
} else {
set rc [catch { set config($fd) [open $config($tty) r+] } err]
if {$rc == 0} {
set rc [catch { fconfigure $config($fd) -mode $config(baud) } err]
}
}
if {$rc == 0} {
fconfigure $config($fd) -buffering none -blocking 1 -translation binary
Message $tty "OK ($config($fd) = $key) -- reading input"
fileevent $config($fd) readable [list Serwatch::Read $fd $key]
} else {
Message ERROR $err
set ok 0
}
}
}
if {!$ok} {
Serwatch::Close
return 0
}
return 1
}
proc Serwatch::Dlg {} {
variable config
if {![winfo exists $config(win)]} {
toplevel $config(win)
wm title $config(win) "Serial Watcher"
wm resizable $config(win) 0 1
}
if {$config(win) == "."} {
set frm .swf
} else {
set frm $config(win).swf
}
font create AsciiFont -family $config(font) -size $config(fsize) -slant roman -weight bold
pack [frame $frm] -fill both -expand yes
pack [scrollbar $frm.sb -command "$frm.txt yview" -takefocus 0] \
-side right -expand 0 -fill y
pack [text $frm.txt -font AsciiFont -width 80 -height 25 \
-state disabled -wrap none -yscrollcommand "$frm.sb set"] \
-side right -expand 1 -fill both
set config(txw) $frm.txt
set config(sbw) $frm.sb
}
proc Serwatch::Message { key {str ""} } {
variable config
if {$key != $config(key)} {
set sec [clock seconds]; set msec [clock clicks -milliseconds]
set tstr [clock format $sec -format %H:%M:%S]
append tstr .[format %03u [expr {($msec - $config(time0)) % 1000}]]
set out "\n$tstr $key $str"
set config(olen) [string length $out]
incr config(lcnt)
} else {
set out " $str"
incr config(olen) [string length $out]
if {$config(olen) >= 75} { set key * }
}
if {$config(win) != {}} {
if {$config(txw) == {}} {
Serwatch::Dlg
}
catch {
set scrpos [lindex [$config(txw) yview] 1]
$config(txw) configure -state normal
$config(txw) insert end $out
$config(txw) configure -state disabled
if {$scrpos == 1} { $config(txw) see end }
if {$config(lcnt) > 2500} {
$config(txw) configure -state normal
$config(txw) delete 0.0 9.0
$config(txw) configure -state disabled
incr config(lcnt) -10
}
}
} else {
puts -nonewline stdout $out; flush stdout
}
if {$config(fdlog) != {}} {
puts -nonewline $config(fdlog) $out
if {$key == "timeout"} { flush $config(fdlog) }
}
set config(key) $key
}
package provide Serwatch 1.2