Read data from serial port - bash script

Hi!

I have a long experience in programming under Visual Basic, PHP, Javascript and microcontroller, but I'm absolutly newbie in bash script, so thanks in advance for your help !

Anyway, I've a home server with Nas4Free (based on FreeBSD 9.2-RELEASE-p3 (kern.osreldate: 902001) ) and I'm trying to write a simple bash script that reads datas from serial port and write those datas to a text file. I've succeed in writing in a text file, but I've lot of problems with serial port !

This is the configuration of the serial port : speed is 1200, 1 start bit "0", 7 bits per character, 1 parity even and 1 stop bit
This is the configuration I've tested for ttyu0 :
Code:
stty -f /dev/ttyu0.init 1200 cs7 -parodd parenb -cstopb -icanon -iexten -ixon -ixoff -crtscts cread clocal echo -echoe echok -echoctl
Then checked with
Code:
stty -a -f /dev/ttyu0.init
returns
Code:
speed 1200 baud; 0 rows; 0 columns;
lflags: -icanon isig -iexten echo -echoe echok echoke -echonl -echoctl
-echoprt -altwerase -noflsh -tostop -flusho -pendin -nokerninfo
-extproc
iflags: -istrip icrnl -inlcr -igncr -ixon -ixoff ixany imaxbel -ignbrk
brkint -inpck -ignpar -parmrk
oflags: opost onlcr -ocrnl tab0 -onocr -onlret
cflags: cread cs7 parenb -parodd hupcl clocal -cstopb -crtscts -dsrflow
-dtrflow -mdmbuf
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
eol2 = <undef>; erase = ^?; erase2 = ^H; intr = ^C; kill = ^U;
lnext = ^V; min = 1; quit = ^\; reprint = ^R; start = ^Q;
status = ^T; stop = ^S; susp = ^Z; time = 0; werase = ^W;
To be sure that datas are send, I've put a spy module on the link cable to watch datas ; all is ok.

First, I've tried to send datas from FreeNas to Hyperterminal in Windows with
Code:
echo 123 > /dev/ttyu0
and everything is ok.

Then, I've tried to read datas on FreeNas from Hyperterminal with a small bash script :
Code:
#!/bin/sh
read ligne < /dev/ttyu0
/bin/echo $ligne
But the script run without doing anything, and stop after about 5 mn (maybe the watchdog ...)

After having read lots of forum, man page and tutorial, nothing really happen, except once when I wrote
Code:
kill -HUP 1
before running the previous script. I was happy as I thought that I've found the solution, but I restarted the computer and test this solution one more time, then it didn't work any more ...

I've tested
Code:
cu -l /dev/ttyu0
and after a few seconds it returns link down

I don't know what to do now ! It's so easy on windows or microcontroller ! Just initialize the serial port, open it then read the buffer ...
I've tested so many things that I don't know in wich way I have to go now ... :(
 
Hi !

So I've made some progress since yesterday.
I've noticed that when I start the server with something plug on the serial port, ttyu0 is not accessible at all (read or write). But when I start the server with nothing plug on the serial, then plug something on it, I can write with no problem, and read with this script :
Code:
#!/bin/sh
kill -HUP 1
stty -f /dev/ttyu0.init 1200 cs7 -parodd parenb -cstopb -icanon -iexten -ixon -ixoff -crtscts cread clocal echo -echoe echok -echoctl
for i in 1 2 3 4 5 6 7 8 9 10
do
	# /bin/echo $i
	read ligne < /dev/ttyu0
	/bin/echo $ligne
done
I don't understand why. Is there any way to restart, reinitialize or release the serial port? That's should be a solution ...

I've noticed something else: with this loop, some data is missing; is there another way of reading entire data or buffer on serial port?

One last thing, this is a list of processes running on startup :

Code:
 PID USERNAME      THR PRI NICE   SIZE    RES STATE   C   TIME   WCPU COMMAND
 3375 root            1  27    0 91804K 18280K piperd  0   0:00  1.17% php-cgi
 2376 root            1  20    0 78644K 11544K select  0   0:00  0.00% smbd
 2072 transmission    3  20    0 57864K  6612K nanslp  1   0:00  0.00% transmission-daemon
 2519 root            1  52    0 35316K  5112K kqread  1   0:00  0.00% lighttpd
 2256 root            1  52    0 76404K 10656K select  1   0:00  0.00% smbd
 2190 root            1  20    0 12084K  1636K select  1   0:00  0.00% powerd
 2150 root            1  20    0 14272K  2168K select  0   0:00  0.00% usbhid-ups
 2253 root            1  20    0 67576K  7344K select  0   0:00  0.00% nmbd
 2636 root            1  52    0 14508K  3152K pause   1   0:00  0.00% csh
 2152 root            1  20    0 20392K  3688K select  1   0:00  0.00% upsd
 1907 root            1  20    0 12112K  1772K select  1   0:00  0.00% syslogd
 2633 root            1  52    0 45332K  2336K wait    0   0:00  0.00% login
 2187 root            1  20    0 20376K  3816K nanslp  0   0:00  0.00% upsmon
 3426 root            1  28    0 16596K  2304K CPU1    1   0:00  0.00% top
 2645 root            1  52    0 14536K  2688K ttyin   0   0:00  0.00% sh
 2448 root            1  20    0  9944K  1856K select  0   0:00  0.00% mDNSResponderPosix
 2278 root            1  20    0 76404K 10728K select  1   0:00  0.00% smbd
 2634 root            1  52    0 12084K  1676K ttyin   1   0:00  0.00% getty
We can se "ttyin" twice ; is that can interfere with reading datas with my script ?

Thanks in advance !
 
I finally found the solution for the missing datas, it's the way that the READ command deals with reading the ttyu0 "file".

First, I've tested that :
Code:
i=0
while [ $i -le "10" ]
do
	read -r ligne < /dev/ttyu0
	/bin/echo $ligne
	i=$(($i+1))
done
But that doesn't work ; it doesn't return all datas.

Then I've tested that :
Code:
j=0
while read -r line < /dev/ttyu0; do
	/bin/echo $ligne
	j=$(($j+1))
	if [ $j -gt "10" ]; then
		break
	fi
done
But that doesn't work to ; it alwas return the same string.

Finally, I've tested that :
Code:
j=0
while read ligne
do
	/bin/echo $ligne
	j=$(($j+1))
	if [ $j -gt "10" ]; then
		break
	fi
done < /dev/ttyu0
And now all datas are read ! :beergrin

It's in the read(2) manpage where I found a clue :
On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number.
Now I'm trying to understand why it doesn't work when rebooting the computer with something plugged on the serial port ...
 
Hi !
I'm very disappointed, today it doesn't work any more after a reboot ...
I realy don't understand how does serial port work under FreeBSD ...
:(
 
Hi Beeblebrox !
Yes I've seen that topic, and others too about difference between cuau and ttyu.
But in my case, cuau doesn't return anything.
I've made some more test, and it seems that the problem come from the state of some port (DCD ...) on computer start.
Whatever, I've tried to reboot computer twice and now it works every time ...
 
Last edited by a moderator:
Hi !
So I made some progress ... I've put two LEDs on the card to see if there is some activity on the serial port. Then I saw interesting things:
- when the computer starts, LEDs are off.
- when I send cat or read command on ttyu0, LEDs turn on and the computer is locked for about 5 minutes then I can use it again, as if there is a watchdog. But the process cat or ttyin is still running for about 2 minutes then disappears. When it disappears, LEDs turn off.

I think that the problem comes from the way I read data from serial port; as the external source always send data without interrupt, maybe the command runs in a loop and waits for the end which doesn't exist!

Maybe the solution should be running a command which reads only a few characters from ttyu0, but I can't find it. Anybody have an idea?

Thanks in advance.
 
Hi ! thanks for your answer.

This is very strange that sometimes it works, and most of the time it doesn't. I can't understand why.
Maybe under Windows there are some hidden subroutines that deal with the state of the serial port, bur not in bash under Unix ...

Still looking for the solution ...
 
You don't seem to understand the issues mentioned in http://perldoc.perl.org/perlfaq8.html#How-do-I-read-and-write-the-serial-port ;)

The read function from bash :
  • does not handle lock files
  • probably does not open your serial device for both read and write
You need a language like Perl to that allows you to use the low-level system C functions that allow this fine-grained control.

Issue with echo to output data:
  • printing to the serial device could be buffered.
That means that not until a certain amount of data, say 1024 bytes, has been written it will actually be output to the device.

Remember that writing to a serial port under a multi-user system like FreeBSD is much more complicated than on a simple 8 or 16 bit processor board. The bash shell is not fit to do serial port access under a complex OS like FreeBSD.
 
Maybe you could try this Perl script. I got this working on OpenBSD. Don't have time to test on FreeBSD
On one OpenBSD box I run $ tip -v -19200 /dev/ttyU0
On the other one I run the Perl script # ./serial-new.pl
Code:
[cmd=#]./serial-new.pl[/cmd]
speed 19200 baud; 0 rows; 0 columns;
lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl
  -echoprt -altwerase -noflsh -tostop -flusho -pendin -nokerninfo
  -extproc -xcase
iflags: -istrip icrnl -inlcr -igncr -iuclc ixon -ixoff ixany imaxbel
  -ignbrk brkint -inpck -ignpar -parmrk
oflags: opost onlcr -ocrnl -onocr -onlret -olcuc oxtabs -onoeot
cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -mdmbuf
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
  eol2 = <undef>; erase = ^?; intr = ^C; kill = ^U; lnext = ^V;
  min = 1; quit = ^\; reprint = ^R; start = ^Q; status = <undef>;
  stop = ^S; susp = ^Z; time = 0; werase = ^W;
0"
===Serial communicator ===
";
What you type here will be send to /dev/tty00
Exit with CONTROL-C ......
This Perl script forks a child process to send everything you type to the serial port, while the parent process reads the serial port input and prints it to the screen (stdout).
Code:
#!/usr/bin/perl
# j65nko Daemonforums.org
#

#use Fcntl;
use POSIX qw(:termios_h); # Use terminal IO library
use warnings;
use strict;

#my $serial_port = "/dev/ttyU0";
my $serial_port = "/dev/tty00";

# open serial port
#sysopen PORT, $serial_port, (O_RDWR|O_NDELAY|O_NOCTTY) or die "Cannot open $serial_port!";

sub get_settings {
my $settings = system("/bin/stty -a < $serial_port");
print $settings;
}

# Adapted from Solaris example at http://www.tek-tips.com/viewthread.cfm?qid=92014

my $DEBUG = 1;
sub init_serial() {
open (PORT, "+<$serial_port") or die "FATAL ERROR - Cannot open $serial_port";
my $FHport = fileno(PORT);
my $Term = POSIX::Termios->new();
$Term->getattr($FHport);
my $Oterm = $Term->getlflag();
my $Echo = ECHO | ECHOK | ICANON;
my $NoEcho = $Oterm & ~$Echo;

$Term->setlflag($NoEcho);
$Term->setcc(VTIME, 5);
$Term->setcc(VMIN, 0);
$Term->setispeed(&POSIX::B19200); # Set input speed
$Term->setospeed(&POSIX::B19200); # Set output speed
get_settings if $DEBUG ;
}

#get_settings();

init_serial();

print <<END ;
"\n===Serial communicator ===\n";
What you type here will be send to $serial_port
Exit with CONTROL-C ......
END

# Following is adapted from:
# Perl Cookbook recipe 17.10 "Writing bidirectional clients"
# Originally a forking server for TCP sockets
# The parent process reads the serial device and copies it to stdout
# The child process reads stdin and writes it to serial device

my $kidpid;

die "Cannot fork: $1" unless defined($kidpid = fork () );

if ($kidpid) {
# parent process copies serial input to standard output
my $byte;

while ( sysread(PORT, $byte, 1) == 1 ){
print STDOUT $byte
}
kill ("TERM" => $kidpid); # send SIGTERM to child process

} else {
# child process copies standard input to the serial port
my $byte;
my $line;

while ( sysread(STDIN, $byte,1) == 1 ) {
syswrite(PORT, $byte, 1) ;
}
}

exit;
# --- end of script

Code:
[cmd=#]ps -aux | grep perl[/cmd]
root  20999  0.0  1.0  1468  3740 p0  I+  3:25AM  0:00.43 /usr/bin/perl ./serial-new.pl
root  20561  0.0  0.2  1468   916 p0  S+  3:25AM  0:00.01 /usr/bin/perl ./serial-new.pl
 

Attachments

  • serial-new.txt
    2 KB · Views: 604
Hi all !
Sorry for the (very) late reply ... I've finally the answer.
The first problem comes from the peripheral I was trying to read wich was broken (in fact it is the electrical counter of our house). So it has to be replaced.
After that things became easy ; this is the way it works for me (Nas4Free 10.2, FreeBSD 10.2-RELEASE-p5) :

First mistake :
Code:
#!/bin/bash
Serial initialisation :
Code:
stty -f /dev/ttyu0.init 1200 cs7 -parodd parenb -cstopb hupcl cread clocal -crtscts -icanon -inlcr -icrnl -brkint ignpar
Variables :
Code:
TEMP_FILE=/mnt/XXX/temp.txt
COM=/dev/ttyu0
Read and store :
Code:
while read -r -t 5 ligne ; do
XXX
done < $TEMP_FILE
i don't know if this is the cleaner way, but it works now for a year with no problem.
I didn't have the time to watch for PEARL solution, but many thanks everybody for your help and suggestions !
 
I am sorry, but I can't be bothered to read the whole thread. If I post something that you already know, I apologize.

stty settings are valid only as long as process which set them runs. Once it dies, system reverts to default values. I haven't used tty's under FreeBSD, but spent years connecting modems, terminal servers, terminals, serial printers, satellite receivers to Solaris and SCO machines. It was before Internet became widespread.

The UNIX way to manipulate serial ports is to redirect scripts stdin or stdout or both to tty device, like:
$ mySerialScript < /dev/tty04.

stty defaults to manipulate parameters for standard input. So, scripts often look like:

Code:
stty <params> # top of the script, no -f
read ...
echo ...
printf ...


Hope it helps. If not, sorry for disturbance
 
Last edited by a moderator:
Hello Askfor, thanks for your answer.

I'm not very good with bash script, so when I made this script I've been looking a lot of websites to make it work ...
What I understood is that under FreeBSD, the serial port is accessible as a file ...

According to this page :
https://www.freebsd.org/cgi/man.cgi?query=stty&sektion=1
The [-f file] parameter is useful for "Open and use the terminal named by file rather than using standard input."

So according to you, what should be the correct way to do this?
 
I don't know what should you do. I'd say you need both, read and write to device. There is a param in stty command that opens it in read/write mode, you should use it. Also it can be open in blocking and non blocking mode. Latter means it returns empty buffer if there is no input available, rather than waiting for one. You need to specify that in stty, too. Also, you can set a timeout. You can read in character mode and in line mode. I think man page might not be enough reading. Have tried to use 'cu' program interactively ? It might help.
 
Back
Top