i've written a little application that tries to do some
communication over the V.24 line. What i wanted to do is
sending a character over the V.24 line, receiving (processing)
the data, wait for some time, sending a character ...
PSEUDO CODE follows
<--
sendData
processData
wait for 10 secs.
sendData
...
-->
But all i achived was sending the initial data and
receiving the first byte stream from the V.24 line.
I've tested a while with loops and [after] but with
no success :-(
Maybe somebody has a hint for me.
( Expect is not a solution because of using Win2000 :-( )
Tcl Version is: 8.4.0.1 on a Win2000 maschine.
Thanks in advance
Markus Seehofer
<--- Beg. code.
# Set up parameter for V.24
set baudRate 9600
set port_id [open "com1" "RDWR"]
fconfigure $port_id -blocking 0 -buffering none -buffersize 10000 -mode
$baudRate,n,8,1 -translation auto
# Set callback function for processing incoming data
set func_name1 "processData"
fileevent $port_id readable {
$func_name1
}
# Set callback function to send data
#set func_name2 "sendData"
#fileevent $port_id writable {
# $func_name2
#}
# Process incoming data
proc processData {} {
global port_id
if {1} {
gets $port_id line
if { [llength $line] > 0 } {
puts -nonewline "Result= "
puts $line
foreach ch [split $line {}] {
scan $ch %c nch
if { $nch > 33 && $nch < 127 } {
puts -nonewline $ch
}
}
puts \n
#puts $line
}
}
set processData_var 1
}
proc sendData {} {
global port_id
puts $port_id "i"
}
sendData
vwait processData_var
---> End code.
try first to achieve the result you want by simulating the
remote system with a separate TCL script: once the channel
is [open]'ed and [fconfigure]'ed there's no real difference
between a channel referencing the serial port or another
process.
So create a script that simulates the remote system
behaviour writing to its stdout and reading from its stdin;
it'll be something like:
# remote.tcl
fconfigure stdin -buffering none -blocking no
fconfigure stdout -buffering none -blocking no
fileevent stdin readable ...
fileevent stdout writable ...
...
vwait forever
exit 0
# end of file
and the "local" script will execute it like this:
set tclsh [auto_execok tclsh]
set script [file join [file dirname [info script]] remote.tcl]
set cmd [format "|%s %s 2>@stderr" $tclsh $script]
set id [open $cmd RDWR]
fconfigure $id -buffering none -blocking no
and then will attach the event handler:
fileevent $id readable "remote_event_callback $id"
fileevent $id writable "remote_event_callback $id"
proc remote_event_callback { id } {
...
}
I think that the key point you have to understand is you
have to keep track of the current state of the operations:
reading, writing, waiting for 10 secs. You have to store a
state indicator in a static variable (a global or namespace
variable) and use [switch] to select the correct action in
both the "local" and "remote" scripts.
When entering the "waiting" state, you want to detach the
event handlers from the channel:
fileevent $id readable {}
fileevent $id writable {}
(or reconfigure to true the blocking option for the channel
itself, but try detaching first) and then setup a timer
event with the [after] command.
When the timer event triggers your timer-script-callback
you exit the "waiting" state by re-attaching the event
handlers to the channel.
Once you have it working with two TCL processes talking
one to the other, you can try to talk to the serial port.
Ciao,
Marco
--
"Love & Peace" -- Vash the Stampede
See other comments below.
Markus Seehofer schrieb:
>
> # Set up parameter for V.24
> set baudRate 9600
> set port_id [open "com1" "RDWR"]
> fconfigure $port_id -blocking 0 -buffering none -buffersize 10000 -mode
> $baudRate,n,8,1 -translation auto
It has no sense to set the output buffer size "-buffersize 10000"
when "-buffering none".
>
> # Set callback function for processing incoming data
> set func_name1 "processData"
> fileevent $port_id readable {
> $func_name1
> }
I'm surprised that you're getting results with this code.
$func_name1 is a local var which is unknown when the fileevent
handler is called.
Why don't you simply call
fileevent $port_id readable processData
BTW I would prefer
fileevent $port_id readable [list processData $port_id]
proc processData {chan} {
...
}
>
> # Set callback function to send data
> #set func_name2 "sendData"
> #fileevent $port_id writable {
> # $func_name2
> #}
>
>
> # Process incoming data
> proc processData {} {
>
> global port_id
>
> if {1} {
>
> gets $port_id line
The use of [gets] in fileevent handlers is always dangerous
unless you know what you are doing.
In the blocking case it will wait for an end-of-line character
and block if none received.
In your non-blocking case it will destroy the line structure of
received data, because the fileevent usually will fire
when you have received only part of the line.
E.g. when the external device sends "Hello, here I am !",
then you may receive it in 3 fileevents:
Result=Hell
Result=o, her
Result=e I am !
That's why I prefer
set line [read $port_id]
over
gets $port_id line
> if { [llength $line] > 0 } {
> puts -nonewline "Result= "
> puts $line
> foreach ch [split $line {}] {
> scan $ch %c nch
> if { $nch > 33 && $nch < 127 } {
> puts -nonewline $ch
> }
> }
> puts \n
> #puts $line
> }
> }
> set processData_var 1
??? Do you mean
set ::processData_var 1
Note that otherwise you are setting a local var
and your vwait below waits forever.
Is this your problem ?
Otherwise you may start to communicate without fileevents:
proc processData {chan} {
gets $chan line
if { [string length $line] > 0 } {
puts "Result=$line"
binary scan $line c* bytes
puts "Bytes=$bytes"
}
}
proc test {chan cmd} {
puts $chan $cmd
# Give some time for communication and receiving the result
after 500 [list processData $chan]
}
set port_id [open "com1" "RDWR"]
fconfigure $port_id -blocking 0 -buffering none 9600,n,8,1 -translation auto
test $port_id "i"
test $port_id "i"
Regards,
Rolf.
---------------------------------------------------------------
Rolf Schroedter, German Aerospace Center
Remove .nospam to reply: mailto:Rolf.Sc...@dlr.de.nospam
Thanks.
>
> It has no sense to set the output buffer size "-buffersize 10000"
> when "-buffering none".
Ok.
>
> I'm surprised that you're getting results with this code.
> $func_name1 is a local var which is unknown when the fileevent
> handler is called.
Yes, you're right, but it work'ed fine. But only for one time :-(
> Why don't you simply call
> fileevent $port_id readable processData
> BTW I would prefer
> fileevent $port_id readable [list processData $port_id]
> proc processData {chan} {
> ...
> }
Look's good, i'll do this now.
> The use of [gets] in fileevent handlers is always dangerous
> unless you know what you are doing.
> In the blocking case it will wait for an end-of-line character
> and block if none received.
> In your non-blocking case it will destroy the line structure of
> received data, because the fileevent usually will fire
> when you have received only part of the line.
> E.g. when the external device sends "Hello, here I am !",
> then you may receive it in 3 fileevents:
> Result=Hell
> Result=o, her
> Result=e I am !
Yes, that's what i get (and not what i want).
> That's why I prefer
> set line [read $port_id]
> over
> gets $port_id line
But with this i get the same result.
>> if { [llength $line] > 0 } {
>> puts -nonewline "Result= "
>> puts $line
>> foreach ch [split $line {}] {
>> scan $ch %c nch
>> if { $nch > 33 && $nch < 127 } {
>> puts -nonewline $ch
>> }
>> }
>> puts \n
>> #puts $line
>> }
>> }
>> set processData_var 1
>
>
> ??? Do you mean
> set ::processData_var 1
>
> Note that otherwise you are setting a local var
> and your vwait below waits forever.
> Is this your problem ?
Yes, one of many, currently :-(
Now one less problem :-)
>
> Otherwise you may start to communicate without fileevents:
>
> proc processData {chan} {
> gets $chan line
> if { [string length $line] > 0 } {
> puts "Result=$line"
> binary scan $line c* bytes
> puts "Bytes=$bytes"
> }
> }
> proc test {chan cmd} {
> puts $chan $cmd
> # Give some time for communication and receiving the result
> after 500 [list processData $chan]
> }
>
> set port_id [open "com1" "RDWR"]
> fconfigure $port_id -blocking 0 -buffering none -mode 9600,n,8,1 -translation
> auto
>
> test $port_id "i"
> test $port_id "i"
I've tried this, but without the wanted result.
I only get pretty few of the incoming data.
Trying this also dosen't work:
proc processData {chan} {
while {![eof $chan]} {
gets $chan line
if { [string length $line] > 0 } {
puts "Result=$line"
binary scan $line c* bytes
puts "Bytes=$bytes"
}
}
}
Now it seems like i'm (the app.) in an endless loop...
My problem is:
How do i know how many data has arrived. Is there a way to read
the channel until it's empty. How do i know that the channel is
empty. Do i need to use the [eof] command ? I've tried a little
with this, but also no success (see above).
Another question:
Is the script associated with the fileevent called every time
a single character has arrived or only for the first character
arrived ?
Thanks so far (also to Marco for his answer)
Markus Seehofer
... because [read $port_id] returns all bytes currently available.
And that's only part of the string.
> Trying this also dosen't work:
>
> proc processData {chan} {
> while {![eof $chan]} {
> gets $chan line
> if { [string length $line] > 0 } {
> puts "Result=$line"
> binary scan $line c* bytes
> puts "Bytes=$bytes"
> }
> }
> }
Never do this. [eof $chan] for a serial port is always =0
unless you have specified a [fconfigure -eofchar].
That's why your application is in an endless loop.
> Now it seems like i'm (the app.) in an endless loop...
>
> My problem is:
> How do i know how many data has arrived. Is there a way to read
> the channel until it's empty. How do i know that the channel is
> empty. Do i need to use the [eof] command ? I've tried a little
> with this, but also no success (see above).
After one [read $chan] the channel ***is empty***.
But after a while you'll probably have more characters.
If you need to process your input line by line
you have the following choises:
(A) Use blocking [gets] with timeout:
fconfig -blocking 1 -timeout 100
(B) Use none-blocking [read]
Store the received bytes in a ***global buffer***
Check whether there is a complete line:
[llength [split $buffer]] > 1
Then process the complete line:
process [lindex [split $buffer] 0]
And remove the processed line from buffer.
Use (A) only if you are sure that your device sends the rest
of the line quickly after sending the first char,
because during [gets] you'll block your application.
Note at 9600 baud, 1 char = 1 millisecond.
>
> Another question:
> Is the script associated with the fileevent called every time
> a single character has arrived or only for the first character
> arrived ?
Tcls I/O system polls the serial port every 10 milliseconds
(see [fconfigure -pollinterval]). And if there is one or more byte
available then you'll get a fileevent. Thus, If you don't read
***all available bytes ***, you'll get contineous redable fileevents
every 10 msec.
Sorry, this should be
[llength [split $buffer "\n"]] > 1
Hello Rolf,
finally i got it working (for me). But still i'm not sure
whether i'm doing it all right.
<--- Code snippet
set buffer {}
# Set up parameter for V.24
set baudRate 9600
set port_id [open "com1" "RDWR"]
fconfigure $port_id -blocking 0 \
-buffering none -mode $baudRate,n,8,1 -translation auto
# Set callback function for processing incoming data
fileevent $port_id readable [list rxHandler $port_id]
proc rxHandler {chan} {
global buffer
after 5000
set buffer [read $chan]
puts $buffer
processIncomingData
set ::tmp 1
}
proc sendData {} {
global port_id
puts $port_id "i"
}
for {set i 0} {$i < 5} {incr i} {
sendData
vwait tmp
#puts $buffer
after 5000
}
close $port_id
---> Code snippet
Regards and thanks for your help
Markus Seehofer
With that approach you will block your application for 5 seconds
at each fileevent. That may be acceptable for a commandline application
but not for a GUI or another event-driven program.
A good fileevent handler should read all available data and return immediately.
I would suggest to append data to a global buffer and perform some data checking
or even a timed data processing:
proc rxHandler {chan} {
global rxBuffer
append rxBuffer [read $chan]
# Choose one of the following options:
#
# (A) Check the buffer size
#
if {[string length $rxBuffer] > 100} {
puts $rxBuffer
processIncomingData
set ::tmp 1
}
# (B) Check there is at least one complete line received
#
if {[string first \n $rxBuffer] >= 0} {
...
}
# (C) Perform a time execution of processIncomingData
# Avoid double execution of the after callback
#
if {! [info exists ::rxCallback]} {
set ::rxCallback [after 5000 {
processIncomingData
set ::tmp 1
unset ::rxCallback ;# Make place for the next callback
}]
}
}
Regards,
Rolf.
Yes, it is a commandline application and the blocking is annoying.
But it worked so far.
This all sounds very good. And finally i got it working
without blocking. The word "value" is always the last one
i get from the channel. So i read the channel until this word
is received and then i'm ready to send new data over the channel.
<---
proc rxHandler {chan} {
global buffer
append buffer [read $chan]
if { [lsearch -glob $buffer "value"] > 0 } {
puts $buffer
processIncomingData
set ::tmp 1
}
}
--->
Regards and thanks,
Markus Seehofer