expected boolean value error in tcl and arrays - serial-port

I've slowly progressing through my serialport tcl app but hit another wall.
I want to create an array of boolean values to iterate in a for loop.
In the for loop, DTR will send a serial output.
Below I have the following code:
set rs232 [open COM3: r]
fconfigure $rs232 -ttycontrol {DTR 0}
array set values {
0 0
1 1
}
set n [array size values]
set x 0
for {set a 0} {$a <=15} {incr a} {
fconfigure $rs232 -ttycontrol {DTR $values(0)}
wait 1000
fconfigure $rs232 -ttycontrol {DTR $values(1)}
wait 1000
}
I run it and I get the error:
expected boolean value but got "$values(0)"
Can anyone tell me why this is and how do I fix it?

This invocation:
fconfigure $rs232 -ttycontrol {DTR $values(0)}
passes the value "DTR $values(0)" for -ttycontrol to fconfigure. The invocation
fconfigure $rs232 -ttycontrol [list DTR $values(0)]
passes "DTR 0".
The braces prevent substitution of the variable, but the invocation of list enforces it.
Alternatively, you could use one of
fconfigure $rs232 -ttycontrol "DTR $values(0)"
fconfigure $rs232 -ttycontrol [subst {DTR $values(0)}]

Related

Error: can't read server: no such variable when using ltk remotely

I am tinkering around with ltk as it provides the option of running a remote GUI. However, when trying to use the remote GUI I run into issues I do not encounter when running ltk locally:
(in-package :ltk-user)
(defun add-current-investigation-frame (master)
(let* ((frame (make-instance 'frame :master master :width 100 :height 100))
(topic-label (make-instance 'label :text "Current Investigation" :master frame))
(project-label (make-instance 'entry :text "N/A" :master frame))
(action-button (make-instance 'button
:master frame
:text "new investigation")))
(setf (command action-button) (lambda ()
(format t "test~%")
(let ((next-project (nth (random 3) '("A" "B" "N/A"))))
(setf (text project-label) next-project))))
(pack frame)
(pack topic-label :side :top)
(pack project-label :side :top)
(pack action-button :side :top)))
(defun create-main-view ()
(let ((wrapper-frame (make-instance 'frame :master nil)))
(pack wrapper-frame)
(add-current-investigation-frame wrapper-frame)))
(defun create-remote-view (&optional (port 8888))
(Ltk:with-remote-ltk port ()
(create-main-view)))
(defun create-local-view ()
(with-ltk ()
(create-main-view)))
When running (create-local-view) everything works fine and the content of the entry widget changes randomly.
When running (create-remote-view) I get the error message can't read server: no such variable. Why does this error occur and how can I fix this?
I am using the remote.tcl deployed by quicklisp:
#!/usr/bin/wish
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
wm withdraw .
set host localhost
if {[llength $argv] == 2} {
set host [lindex $argv 0]
set port [lindex $argv 1]} else {
set port [lindex $argv 0]}
#puts "connecting to $host $port"
set server [socket $host $port]
set wi [open "|wish" RDWR]
fconfigure $server -blocking 0
fconfigure $wi -blocking 0
fileevent $server readable {set txt [read $server];puts $wi "$txt";flush $wi}
fileevent $wi readable {
if {[eof $wi]} {
close $wi
exit} else {
set txt [read $wi]; puts -nonewline $server $txt;flush $server}}
So I spent some time reading and testing the code, and it appears that it works better with remote-client.tcl than remote.tcl. When working with ltk-remote.lisp, the Lisp side creates a server that may accept multiple clients, each client being a tcl/tk interpreter.
lisp <=== socket stream ===> [ server socket ]
^
|
(wish interpreter)
The lisp side expects the interpreter to maintain a global variable named server. In the case of a local interpreter, this is done in init-wish, where there is set server stdout. In the case of a remote wish, it is expected that the wish interpreter sets this variable itself.
This is the case with remote-client.tcl, and the test applications works well (e.g. ltk-remote::lrtest), except that it adds a .status widget which is never removed. It should be possible to clean up a bit the remote-client.tcl script.
In the case of remote.tcl, the interpreter opens a pair of streams to another wish process:
set wi [open "|wish" RDWR]
It also connects to a server (variable server), and copies inputs from the server to the wish process. Unfortunately, the embedded wish process does not define a server variable:
lisp <=== socket stream ===> [ server socket ]
^
|
(wish interpreter 1)
"server" variable
|
"wi" variable
^
| pipe connection
v
(wish interpreter 2)
no "server" variable
If however you set server to stdout, as explained in the other answer, this assignment is evaluated in the second wish interpreter. The output is sent back to the first wish interpreter, which copies the answer back to the lisp server.
Instead of going through another wish interpreter, I tested locally by using a modified remote-client.tcl that doesn't add any widget:
package require Tk
set host localhost
set port 19790
set server ""
if {[llength $argv] > 0} {
set host [lindex $argv 0]
}
if {[llength $argv] > 1} {
set port [lindex $argv 1]
}
if {[catch {global server; global host; global port; set server [socket $host $port]}]} {
tk_messageBox -icon error -type ok -title "Connection failed!" -message "Cannot connect to server $host port $port."
exit
}
fconfigure $server -blocking 0 -translation binary -encoding utf-8
fileevent $server readable [list sread $server]
set buffer ""
proc getcount {s} {
if {[regexp {^\s*(\d+) } $s match num]} {
return $num
}
}
proc getstring {s} {
if {[regexp {^\s*(\d+) } $s match]} {
return [string range $s [string length $match] end]
}
}
proc process_buffer {} {
global buffer
global server
set count [getcount $buffer]
set tmp_buf [getstring $buffer]
while {($count > 0) && ([string length $tmp_buf] >= $count)} {
set cmd [string range $tmp_buf 0 $count]
set buffer [string range $tmp_buf [expr $count+1] end]
if {[catch $cmd result]>0} {
tk_messageBox -icon error -type ok -title "Error!" -message $result
puts $server "(error: \"$result\")"
flush $server
close $server
exit
}
set count [getcount $buffer]
set tmp_buf [getstring $buffer]
}
}
proc sread {server} {
global buffer
if {[eof $server]} {
tk_messageBox -icon info -type ok -title "Connection closed" -message "The connection has been closed by the server."
close $server
exit
} else {
set txt [read $server];
set buffer "$buffer$txt"
process_buffer
}
}
This is a preliminary answer as I am not entirely sure that this fix does not break anything. I will update this answer in the future to report back on encountered issues. But for now this fixes the issue.
In ltk.lisp there is a function called init-wish which requires an additional line (send-wish "set server stdout")
(defun init-wish ()
(send-lazy
;; print string readable, escaping all " and \
;; proc esc {s} {puts "\"[regsub {"} [regsub {\\} $s {\\\\}] {\"}]\""}
;(send-wish "proc esc {s} {puts \"\\\"[regsub -all {\"} [regsub -all {\\\\} $s {\\\\\\\\}] {\\\"}]\\\"\"} ")
;(send-wish "proc escape {s} {return [regsub -all {\"} [regsub -all {\\\\} $s {\\\\\\\\}] {\\\"}]} ")
(send-wish "package require Tk")
;;; PUT MISSING LINE HERE
(send-wish "set server stdout")
;;; PUT MISSING LINE HERE
(flush-wish)
#+:tk84
(send-wish "catch {package require Ttk}")
#-:tk84
(send-wish "if {[catch {package require Ttk} err]} {tk_messageBox -icon error -type ok -message \"$err\"}")
(send-wish "proc debug { msg } {
global server
puts $server \"(:debug \\\"[escape $msg]\\\")\"
flush $server
} ")
; more code ....
))
Explanation: The function seems to set up the wish interface and actions (confirmed by inserting prints in the remote.tcl). However, as one can see server is referenced in all procs yet it is never declared if we consider all those declarations to be in their own namespace. Consequently, the missing server has to be defined. As all the output is read by fileevent $wi ... and then passed on further, defining server as stdout seemed the most sensible.
It seems to work, however I have no clue if this breaks other stuff

Opening a serialport, transmitting on txd, dtr and rts

Morning all,
I have developed an app with C# and Windows Forms that opens a serialport and transmits on txd, dtr and rts.
I want to be able to do this with tcl/tk but finding serialport tutorials on tcl/tk is proving quite difficult. I did find something on Stackoverflow Here. But when running it:
It says "couldn't open serial "COM7": permission denied"
Does anyone know why permission is denied and how to grant permission? Also does this code even work.
Does anyone have any sample code I can try or can point me to a good understandable source please?
You could have a look at this example that uses a Tcl/tk to read data from a serial port:
############################################
# A first quick test if you have a modem
# open com2: for reading and writing
# For UNIX'es use the appropriate devices /dev/xxx
set serial [open com2: r+]
# setup the baud rate, check it for your configuration
fconfigure $serial -mode "9600,n,8,1"
# don't block on read, don't buffer output
fconfigure $serial -blocking 0 -buffering none
# Send some AT-command to your modem
puts -nonewline $serial "AT\r"
# Give your modem some time, then read the answer
after 100
puts "Modem echo: [read $serial]"
############################################
# Example (1): Poll the comport periodically
set serial [open com2: r+]
fconfigure $serial -mode "9600,n,8,1"
fconfigure $serial -blocking 0 -buffering none
while {1} {
set data [read $serial] ;# read ALL incoming bytes
set size [string length $data] ;# number of received byte, may be 0
if { $size } {
puts "received $size bytes: $data"
} else {
puts "<no data>"
update ;# Display output, allow to close wish-window
}
############################################
# Example (2): Fileevents
set serial [open com2: r+]
fconfigure $serial -mode "9600,n,8,1" -blocking 0 -buffering none -translation binary
fileevent $serial readable [list serial_receiver $serial]
proc serial_receiver { chan } {
if { [eof $chan] } {
puts stderr "Closing $chan"
catch {close $chan}
return
}
set data [read $chan]
set size [string length $data]
puts "received $size bytes: $data"
}
(disclaimer: this is taken verbatim from here)
EDIT: I am sorry but I do not have enough reputation to comment, but it is probably a good idea to specify the platform to narrow down what the permission issues might be related to.

adding transmit delay in COM port from tcl

How to add transmit delay in tcl script for the COM port?
This is the command we use to open the com port how to set the delay (msec/char) ?
fconfigure $::gComPort -mode $::gSerialPortSpeed,n,8,1 -blocking 1 -buffering none \
-translation binary -ttycontrol {BREAK 0} -handshake none
How to add delay to 1msec/char in the below marked transmit delay section from tcl ?
There is no function to designate inter-character delay when sending as specification of hardware/device driver of serial port.
It is necessary to implement it by the application itself or middleware/library that goes between the application and the device driver.
If TCL, call the after command and 1 byte write sequentially and loop it by the length of the transmitted data.
It is like this in this article, it has a delay of 10ms.
proc SendCmd {channel command} {
global output debug
set letter_delay 10
set commandlen [string length $command]
for {set i 0} {$i < $commandlen} {incr i} {
set letter [string index $command $i]
after $letter_delay
puts -nonewline $channel $letter
if {$debug(dutConfig) == 1} {puts -nonewline $output "$letter"}
}
after $letter_delay
puts -nonewline $channel "\n"
if {$debug(dutConfig) == 1} {puts $output ""}
after 500
flush $channel
}

Reading serial port in Rebol3

I am using Rebol3 v3.0.99.4.20 that has both the /View and serial functionality.
I am opening a port with:
ser: open serial://ttyUSB0/9600
Then, I set up my asynchronous handler:
ser/awake: func [event /local p][
p: event/port
switch event/type [
lookup [open p]
connect [write p to-binary request]
read [
result: to-string p/data
close p
return true
]
wrote [read event/port]
]
false
]
The problem I have now is that I cannot figure out how to read data from the serial port. I always only get back the last command I wrote to the serial port in ser/data.
For example:
>> ser: open serial://ttyUSB0/9600
>> write ser "debug on^/"
>> read ser
== "debug on^/"
That looks OK so far, but this is how the serial device operates using the Linux command, 'screen':
My input:
debug on
The serial device response:
Debug messages enabled.
>
However, I never can read the "Debug messages enabled." text.
>> read ser
== "debug on^/"
>> wait ser
== none
>> read ser
== "debug on^/"
>> copy ser/data
== "debug on^/"
Not sure what I'm missing.
In Rebol2, it is much more straightforward, but not asynchronous:
>> system/ports/serial
== [com1 com2 com4]
>> ser: open/no-wait serial://port3/9600/8/none/1
>> insert ser "debug on^/"
>> copy ser
== "debug on^/Debug messages enabled.^/>"
>> copy ser
== ""
A 2nd copy doesn't return anything because the first copy cleared the serial buffer. If data was streaming to the serial port, additional 'copy commands would return additional data from the serial buffer. But it doesn't work this way in Rebol3.
Found this info in the archives of a chat group:
ser: open serial://ttyUSB0/9600
written: false
ser/awake: func [evt][
switch evt/type [
read [
attempt [print to-string evt/port/data]
read evt/port
return true
]
wrote [
written: true
return true
]
]
false
]
write ser "debug on^/"
while [not written][
wait [ser 1]
]
read ser
wait [ser 1]
The event loop you provided in your question actually should read the data for you. If you want to keep reading data, you should not exit the loop with return true but do another read in the read event. You should process the data inside the event loop.

batch file load balacing code query

I am trying to run a bat file code from the following reference:
Detecting batch IP conflict
however, i am getting an error:
TRUE - invalid alias verb
-1 was unexpected at this time.
Could someone kindly explain this to me please. thanks. sample code attached below
#echo off
setlocal
:: Host to ping
set primary=x.x.x.1
:: Ping with options (1 ping sent per loop, wait 500 ms for timeout)
set ping_options=-n 1 -w 500
:: Fail over after x ping failed responses
set fail_limit=5
:loop
:: Ping x.x.x.1. Test for "reply from". If success, set failures=0; otherwise, increment failures
( ping %ping_options% %primary% 2>&1 | find /i "reply from" >NUL && set failures=0 ) || set /a "failures+=1"
:: If failures >= limit, switch IP
if failures GEQ %fail_limit% call :switch
:: Pause for a second and begin again.
ping -n 2 0.0.0.0 >NUL
goto loop
:: Switch subroutine
:switch
:: Get current IPv4 address
for /f "tokens=2 delims={}," %%I in ('wmic nicconfig where ipenabled="TRUE" get ipaddress /format:list') do set IP=%%~I
:: If the last character if the current IP is 1, switch to 2 (or vice versa)
if %IP:~-1%==1 ( set other=%IP:0,-1%2 ) else set other=%IP:0,-1%1
:: Perform the switch
netsh int ipv4 set address name="Local Area Connection" source=static address=%other% mask=255.255.255.0 gateway=none
All you need to do is escape the equal sign in the wmic command.
for /f "tokens=2 delims={}," %%I in (
'wmic nicconfig where ipenabled^="TRUE" get ipaddress /format:list'
) do set IP=%%~I

Resources