I need to communicate with a serial port inside a Perl script. I need to send characters and read the input and search for strings.
What is the simplest way to achieve this? By using "expect" or by opening the /dev/ttys0 device in Perl itself ? Or some other method ?
I prefer to use perl but I don't know if it is simple and featured as expect.
my $port = new Device::SerialPort("/dev/ttyS0");
$port->user_msg(ON);
$port->baudrate(9600);
$port->parity("none");
$port->databits(8);
$port->stopbits(1);
$port->handshake("xoff");
$port->write_settings;
$port->lookclear;
$port->write("some command to com-port");
my $answer = $port->lookfor;
# or my $answer=$port->read(255);
You can try Win32::SerialPort for Win32 and Device::SerialPort for linux.
IO::Termios
Device::SerialPort
Related
I have a embedded linux box with perl 5.10 and a GSM modem attached.
I have written a simple perl script to read/write AT commands through the modems device file (/dev/ttyACM0).
If i write a simle command like "ATZ\r" to the modem and wait for a response I receive very odd data like "\n\n\nATZ\n\n0\n\nOK\n\n\n\n\nATZ\n\n\n\n..." and the data keeps coming in. It almost seems like the response is garbled up with other data.
I would expect something like "ATZ\nOK\n" (if echo is enabled).
If i send the "ATZ" command manually with e.g. minicom everything works as expected.
This leads me to think it might be some kind of perl buffering issue, but that's only guessing.
I open the device in perl like this (I do not have Device::Serialport on my embedded linux perl installation):
open(FH, "+<", "/dev/ttyACM0") or die "Failed to open com port $comport";
and read the response one byte at a time with:
while(1) {
my $response;
read(FH, $response, 1);
printf("hex response '0x%02X'\n", ord $response);
}
Am I missing some initialization or something else to get this right?
Regards
Klaus
I don't think you need the while loop. This code should send the ATZ command, wait for the response, then print the response:
open(FH, "+>", "/dev/ttyACM0") or die "Failed to open com port $comport";
print FH ("ATZ\n");
$R = <FH>;
print $R;
close(FH);
It may be something to do with truncation. Try changing "+>" into "+<".
Or it may be something to do with buffering, try unbuffering output after your open():
select((select(FH), $| = 1)[0]);
Thanks for your answer. Although not the explicit answer to my question it certainly brought me on the right track.
As noted by mti2935 this was indeed not a perl problem, but a mere tty configuration problem.
Using the "stty" command with the following parameters set my serial port in the "expected" mode:
-opost: Disable all output postprocessing
-crnl: Do not translate CR to NL
-onlcr: Do not translate NL to CR NL
-echo: Do not echo input (having this echo enabled and echo on the modem itself gave me double echoes resulting in odd behaviour in my script)
It is also possible to use the combination setting "raw" to set all these parameters the correct way.
-
I am somewhat familiar with various ways of calling a script from another one. I don't really need an overview of each, but I do have a few questions. Before that, though, I should tell you what my goal is.
I am working on a perl/tk program that: a) gathers information and puts it in a hash, and b) fires off other scripts that use the info hash, and some command line args. Each of these other scripts are available on the command line (using another command-line script) and need to stay that way. So I can't just put all that into a module and call it good.I do have the authority to alter the scripts, but, again, they must also be usable on the command line.
The current way of calling the other script is by using 'do', which means I can pass in the hash, and use the same version of perl (I think). But all the STDOUT (and STDERR too, I think) goes to the terminal.
Here's a simple example to demonstrate the output:
this_thing.pl
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Tk;
my $mw = MainWindow->new;
my $button = $mw->Button(
-text => 'start other thing',
-command => \&start,
)->pack;
my $text = $mw->Text()->pack;
MainLoop;
sub start {
my $script_path = 'this_other_thing.pl';
if (not my $read = do $script_path) {
warn "couldn't parse $script_path: $#" if $#;
warn "couldn't do $script_path: $!" unless defined $read;
warn "couldn't run $script_path" unless $read;
}
}
this_other_thing.pl
#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
print "Hello World!\n";
How can I redirect the STDOUT and STDIN (for interactive scripts that need input) to the text box using the 'do' method? Is that even possible?
If I can't use the 'do' method, what method can redirect the STDIN and STDOUT, as well as enable passing the hash in and using the same version of perl?
Edit: I posted this same question at Perlmonks, at the link in the first comment. So far, the best response seems to use modules and have the child script just be a wrapper for the module. Other possible solutions are: ICP::Run(3) and ICP in general, Capture::Tiny and associated modules, and Tk::Filehandle. A solution was presented that redirects the output and error streams, but seems to not affect the input stream. It's also a bit kludgy and not recommended.
Edit 2: I'm posting this here because I can't answer my own question yet.
Thanks for your suggestions and advice. I went with a suggestion on Perlmonks. The suggestion was to turn the child scripts into modules, and use wrapper scripts around them for normal use. I would then simply be able to use the modules, and all the code is in one spot. This also ensures that I am not using different perls, I can route the output from the module anywhere I want, and passing that hash in is now very easy.
To have both STDIN & STDOUT of a subprocess redirected, you should read the "Bidirectional Communication with Another Process" section of the perlipc man page: http://search.cpan.org/~rjbs/perl-5.18.1/pod/perlipc.pod#Bidirectional_Communication_with_Another_Process
Using the same version of perl works by finding out the name of your perl interpreter, and calling it explicitly. $^X is probably what you want. It may or may not work on different operating systems.
Passing a hash into a subprocess does not work easily. You can print the contents of the hash into a file, and have the subprocess read & parse it. You might get away without using a file, by using the STDIN channel between the two processes, or you could open a separate pipe() for this purpose. Anyway, printing & parsing the data back cannot be avoided when using subprocesses, because the two processes use two perl interpreters, each having its own memory space, and not being able to see each other's variables.
You might avoid using a subprocess, by using fork() + eval() + require(). In that case, no separate perl interpreter will be involved, the forked interpreter will inherit the whole memory of your program with all variables, open file descriptors, sockets, etc. in it, including the hash to be passed. However, I don't see from where your second perl script could get its hash when started from CLI.
I'm using a serial device for a project, and what I'm trying to accomplish PC side, is listening for a command sent by the serial device, interpreting the query, running some code depending on the query, and transmitting back the result.
To be honest I tried out using PHP as the listener, and it works, unfortunately the infinite loop required to make the script act as a receiver, loads the CPU to 25%. So it's not really the best option.
I'm using cygwin right now, I'd like to create a bash script using linux native commands.
I can receive data by using:
cat /dev/ttyS2
And send a response with:
echo "command to send" > /dev/ttyS2
My question is, how do I make an automated listener to be able to receive and send data? The main issue I have, is actually how do I stop the cat /dev/ttyS2 command once information was received, put it into a variable which then I could compare with a switch, or a series of if else then blocks. Afterwards send back a response and start the cycle all over again?
Thanks
Is this not what you're looking for?
while read -r line < /dev/ttyS2; do
# $line is the line read, do something with it
# which produces $result
echo $result > /dev/ttyS2
done
It's possible that reopening the serial device on every line has some side-effect, in which case you could try:
while read -r line; do
# $line is the line read, do something with it
# which produces $result
echo $result > /dev/ttyS2
done < /dev/ttyS2
You could also move the output redirection, but I suspect you will have to turn off stdout buffering.
To remain fairly system independent, use a cross platform programming language: like Python, use a cross platform serial library like : pySerial and do the processing inside a script. I have used pySerial and I could run the script cross platform with almost no changes in source code. By using BASH you're limiting yourself a fair little.
If you use right tools, it is possible to actually have your CPU usage to be exactly 0 when your device does not have any output.
To accomplish this, you should use some higher level language (Perl, Python, C/C++ would do, but not bash) and use select loop on top of file handle of your serial device. This is an example for Perl http://perldoc.perl.org/IO/Select.html, but you can use any other language as long as it has support for select() syscall.
I would recommend to use C/C++ with Qt 5.1.1, it's really easy and if you are familiar with the framework it'll be a piece of cake!!!
Here you can find more information and here more helpful examples, give it a try,
it's really pain free!! Also you can develop on win and then port your code to linux...straight forward.
Declare an object like this:
QSerialPort mPort; //remember to #include <QtSerialPort/QSerialPort>
//also add QT += serialport to your .pro file
Then add this code:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
setupUi(this);
connect(this->pushButton,SIGNAL(clicked()),this,SLOT(sendData()));
mPort.setPortName("ttyS0");
mPort.setBaudRate(QSerialPort::Baud115200);
mPort.setParity(QSerialPort::EvenParity);
if(!mPort.open(QSerialPort::ReadWrite))
{
this->label->setText(tr("unable to open port, %1").arg(mPort.error()));
}
connect(&(this->mPort),SIGNAL(readyRead()),this,SLOT(readData()));
}
void MainWindow::sendData()
{
QByteArray data = lineEdit->text().toLatin1();
if(mPort.isOpen())
{
mPort.write(data);
}
else
{
this->label->setText(tr("port closed %1").arg( mPort.error()));
}
}
void MainWindow::readData()
{
QString newData;
int bread=0;
while(bread < mPort.bytesAvailable() ){
newData += mPort.readAll();
bread++;
}
this->textEdit->insertPlainText("\n" + newData);
}
How to listen to the spoken DTMD digit every time the sound card capture one?
The objective is radio controlling my pc and interfaces activities dialing dtmf tones via a hand-held transceiver.
I used multimon to hear DTMF tones
I tried to use awk to filter digits and proceed accordingly.
For example, if I key "0" from the radio the system must reboot, etc, but first confirming the operation. " The computer will reboot, send # to confirm"...
I tried to use espeak for a voice confirmation to the remote radio.
The radio connected to the pc soundcard receives the commands and transmits the responses.
I do not simply know how to nest multimon within awk within espeak.
Multimon itself doesnt let me do anything with its stdout because its running ( do not terminate after hearing a digit, which is indeed right).
It would be extremely helpful if I knew how to just speak each digit, without exiting the natural multimon loop.
Say, multimon -a DTMF | awk'{print}' espeak -stdin
If this simply worked!
Is it possible to do? Any help wellcome.
3 years passed and still no advance in linux DTMF decoding.
Once I didnt see any DTMF Radio Controlling project in Linux, I plan to publish this shall I can solve this issue.
Thanks / Mario/ sao paulo brazil
I believe that my answer is out of date and perhaps you have found how to solve this.
I faced the same issue and figured out that the problem is in multimon. When you pipe the stdout from multimon to another program, multimon does not flush the verbose output properly.
Try to patch and recompile multimon, adding a "fflush(stdout);", like this:
(unixinput.c , around line 71 )
void verbprintf(int verb_level, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
if (verb_level <= verbose_level) {
vfprintf(stdout, fmt, args);
fflush(stdout); //ADD THIS LINE!!!!!!!!!!!!!!!
}
va_end(args);
}
Best regards,
-Eduardo
You can use the system() function to launch espeak from your awk script.
#OP, i am not clear about your question, but seeing that system() interests you, here's how you call external command in awk
multimon -a DTMF | awk '{
cmd="espeak "$0 #$0 comes from the multimon output
system(cmd)
}
'
I have a script that needs to know what username it is run from.
When I run it from shell, I can easily use $ENV{"USER"}, which is provided by bash.
But apparently - then the same script is run from cron, also via bash - $ENV{"USER"} is not defined.
Of course, I can:
my $username = getpwuid( $< );
But it doesn't look nice - is there any better/nicer way? It doesn't have to be system-independent, as the script is for my personal use, and will be only run on Linux.
Try getting your answer from several places, first one wins:
my $username = $ENV{LOGNAME} || $ENV{USER} || getpwuid($<);
crontab sets $LOGNAME so you can use $ENV{"LOGNAME"}. $LOGNAME is also set in my environment by default (haven't looked where it gets set though) so you might be able to use only $LOGNAME instead of $USER.
Although I agree with hacker, don't know what's wrong about getpwuid.
Does this look prettier?
use English qw( −no_match_vars );
my $username = getpwuid $UID;
Sorry, why doesn't that "look nice"? That's the appropriate system call to use. If you're wanting an external program to invoke (e.g. something you could use from a bash script too), there are the tools /usr/bin/id and /usr/bin/whoami for use.
Apparently much has changed in Perl in recent years, because some of the answers given here do not work for fetching a clean version of "current username" in modern Perl.
For example, getpwuid($<) prints a whole bunch of stuff (as a list in list context, or pasted-together as a string in scalar context), not just the username, so you have to use (getpwuid($<))[0] instead if you want a clean version of the username.
Also, I'm surprised no one mentioned getlogin(), though that doesn't always work. For best chance of actually getting the username, I suggest:
my $username = getlogin() || (getpwuid($<))[0] || $ENV{LOGNAME} || $ENV{USER};