Reading serial port in Rebol3 - serial-port

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.

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.

Go stdout stream from async command

I want to use GO to run an asynchronous command on windows 10. The command I am running is blocking, and if run directly from the terminal it provides a constant steam of status messages until ctrl-c
I want to run that command from GO via exec and catch the output from the exec command to the terminal in real time, i.e. not only when the GO application terminates.
I have tried numerous examples but with not success, I just get a blank terminal and even after exiting the GO application, I don't see the output from the command I executed.
You can use cmd.StdoutPipe to do that:
cmd := exec.Command(cmdName, cmdArgs...)
cmdReader, _ := cmd.StdoutPipe()
scanner := bufio.NewScanner(cmdReader)
done := make(chan bool)
go func() {
for scanner.Scan() {
fmt.Printf(scanner.Text())
}
done <- true
}()
cmd.Start()
<- done
err = cmd.Wait()
You can use io.MultiWriter to capture output and forward it to stdout and stderr.
var stdoutBuf, stderrBuf bytes.Buffer
cmd := exec.Command("/some-command")
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
err := cmd.Start() // Starts command asynchronously
if err != nil {
fmt.Printf(err.Error())
}

How to write to stdin of external process in Elixir

Is it possible to write to stdin of external process In Elixir? Is NIF the only option right now?
The process that started from Elixir, blocks and wait for user input:
pid = spawn(fn ->
System.cmd("sh", [
Path.join([System.cwd, "sh", "wait_for_input"]),
"Hello world"
])
end)
I would like to achieve something like that
IO.write pid, "Hello"
IO.write pid, "Hello again"
And this is the script
#!/bin/sh
while read data
do
echo $data >> file_output.txt
done
You can use Port for this. Note that the read builtin of sh will only get data when a newline character is sent to sh, so you need to add that whenever you want the buffered data to be sent to read.
$ cat wait_for_input
while read data
do
echo $data >> file_output.txt
done
$ iex
iex(1)> port = Port.open({:spawn, "sh wait_for_input"}, [])
#Port<0.1260>
iex(2)> Port.command port, "foo\n"
true
iex(3)> Port.command port, "bar\n"
true
iex(4)> Port.close(port)
true
$ cat file_output.txt
foo
bar

qt read data from serial port just after open by another program

I'm using Qt version 5.5.1 from windows 8.1.
When I run qtserialport terminal example,
program connects to port successfully, but does not receive any data.
But when I close this program and open Hercules_3-2-6 Application (rs232 terminal software), that application read data,
and after close Hercules_3-2-6 application and open terminal example again, this program works and reads data until restarting computer.
I repeat this process many times.
But terminal project does not receive any data after restarting system until port opens one time by Hercules_3-2-6 Application.
Specification of port:
Name: COM3,
Baud Rate: 9600,
Data bits: 8,
Parity: None,
Stop bits: 1,
Flow control: None
void MainWindow::openSerialPort()
{
SettingsDialog::Settings p = settings->settings();
serial->setPortName(p.name);
serial->setBaudRate(p.baudRate);
serial->setDataBits(p.dataBits);
serial->setParity(p.parity);
serial->setStopBits(p.stopBits);
serial->setFlowControl(p.flowControl);
if (serial->open(QIODevice::ReadWrite)) {
console->setEnabled(true);
console->setLocalEchoEnabled(p.localEchoEnabled);
ui->actionConnect->setEnabled(false);
ui->actionDisconnect->setEnabled(true);
ui->actionConfigure->setEnabled(false);
showStatusMessage(tr("Connected to %1 : %2, %3, %4, %5, %6")
.arg(p.name).arg(p.stringBaudRate).arg(p.stringDataBits)
.arg(p.stringParity).arg(p.stringStopBits).arg(p.stringFlowControl));
} else {
QMessageBox::critical(this, tr("Error"), serial->errorString());
showStatusMessage(tr("Open error"));
}
}
void MainWindow::readData()
{
QByteArray data = serial->readAll();
console->putData(data);
}
Simply you must configure serial port after that you have open it. If you open the port after these instruction:
serial->setBaudRate(p.baudRate);
serial->setDataBits(p.dataBits);
serial->setParity(p.parity);
serial->setStopBits(p.stopBits);
serial->setFlowControl(p.flowControl);
they was ignored. Your second program configure right the serial port for you and the configuration remain when you start your program.
Try this:
void MainWindow::openSerialPort()
{
SettingsDialog::Settings p = settings->settings();
serial->setPortName(p.name);
if (serial->open(QIODevice::ReadWrite)) {
serial->setBaudRate(p.baudRate);
serial->setDataBits(p.dataBits);
serial->setParity(p.parity);
serial->setStopBits(p.stopBits);
serial->setFlowControl(p.flowControl);
console->setEnabled(true);
console->setLocalEchoEnabled(p.localEchoEnabled);
ui->actionConnect->setEnabled(false);
ui->actionDisconnect->setEnabled(true);
ui->actionConfigure->setEnabled(false);
showStatusMessage(tr("Connected to %1 : %2, %3, %4, %5, %6")
.arg(p.name).arg(p.stringBaudRate).arg(p.stringDataBits)
.arg(p.stringParity).arg(p.stringStopBits).arg(p.stringFlowControl));
} else {
QMessageBox::critical(this, tr("Error"), serial->errorString());
showStatusMessage(tr("Open error"));
}
}
void MainWindow::readData()
{
QByteArray data = serial->readAll();
console->putData(data);
}
You must pay attention when configuring a serial port, any option can return true or false; best practice want to check and manage every error that can be returned.

Resources