Perl Serial port access [Windows 10] - windows

I do some small project with Neo-6M GPS module datasheet, in Windows 10 to tracks GPS satellites.
A code snippet below demonstrates the communication core of the program.
I found that sometimes at program's start it does not communicate with GPS module and I have to interrupt it by pressing Ctrl+C a few times, to be able re-start it and to establish communication between the program and GPS module.
Sometimes it can take a few tries before the program will read data from GPS module.
GPS module connected to the computer through USB to Serial module cp2102, cp2102 datasheet.
GPS module and drivers work properly -- confirmed with u-center software.
Can somebody spot a cause of the described problem with interaction between the program and GPS module?
use strict;
use warnings;
use feature 'say';
use Time::HiRes qw(usleep);
use Win32::SerialPort;
use Win32::Process;
use Win32::Console::ANSI qw/Cls Title Cursor/;
use Term::ANSIScreen qw/:color :cursor :screen/;
use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT);
use Data::Dumper;
my $debug = 1;
my $port_name = 'COM4';
my $baudrate = 9600;
my $databits = 8;
my $parity = 'none';
my $stopbits = 1;
my $portObj = new Win32::SerialPort($port_name)
|| die "Can't open $port_name: $^E\n"; # $quiet is optional
$portObj->baudrate($baudrate);
$portObj->databits($databits);
$portObj->parity($parity);
$portObj->stopbits($stopbits);
sub sig_handler {
Cls();
cursor_mode('on');
$portObj->lookclear();
$portObj->close();
exit 0;
}
cursor_mode('off');
while(1) {
my $line = $portObj->lookfor();
if( $line ) {
{
local $/ = "\r";
chomp $line;
}
say "[$line]" if $debug;
# Some data processing takes place
} else {
usleep(50); # Allocate time for other processes to run
}
}
# Data processing subroutines
# Positioned output to terminal window
sub cursor_mode {
my $mode = shift;
print "\e[?25l" if $mode eq 'off';
print "\e[?25h" if $mode eq 'on';
}

You have to log into the GPS module host and restart the GPS module daemon (or otherwise kill the stale connection). Doing a ctrl-c on the program isn't enough. Sometimes you're able to reestablish a connection because the preceding connection timed out.

Related

print an image from command line and await print job completion on Windows

I needed to write a solution to write data on and then print RFID labels en-masse, each generated as .png images from a template python script and data taken from a database or excel file.
To print the program simply calls the relative system utility (CUPS on unix systems) using subprocess.check_call(print_cmd) passing the image file (saved on a ram-mounted file system for minimal disk usage)
Now, it also needs to run on Windows systems, but there is not really a decent system utility for that, and solutions under a similar question command line tool for print picture? don't account for print-job completion or if the job results in an error, the margins are all screwed and the image is always rotated 90 degrees for some reason.
How can I sanely print an image using a command or a script in Windows and wait for it to complete successfully or return an error if the job results in an error?
Possibly with no dependencies
If you can install dependencies, there are many programs that offer a solution out-of-the-box.
The only sane way i could find to solve this issue with no dependencies is by creating a powershell script to account for this
[CmdletBinding()]
param (
[string] $file = $(throw "parameter is mandatory"),
[string] $printer = "EXACT PRINTER NAME HERE"
)
$ERR = "UserIntervention|Error|Jammed"
$status = (Get-Printer -Name $printer).PrinterStatus.ToString()
if ($status -match $ERR){ exit 1 }
# https://stackoverflow.com/a/20402656/17350905
# only sends the print job to the printer
rundll32 C:\Windows\System32\shimgvw.dll,ImageView_PrintTo $file $printer
# wait until printer is in printing status
do {
$status = (Get-Printer -Name $printer).PrinterStatus.ToString()
if ($status -match $ERR){ exit 1 }
Start-Sleep -Milliseconds 100
} until ( $status -eq "Printing" )
# wait until printing is done
do {
$status = (Get-Printer -Name $printer).PrinterStatus.ToString()
if ($status -match $ERR){ exit 1 }
Start-Sleep -Milliseconds 100
} until ( $status -eq "Normal" )
I would then need to slightly modify the print subprocess call to
powershell -File "path\to\print.ps1" "C:\absolute\path\to\file.png"
Then there are a couple of necessary setup steps:
(discaimer, I don't use windows in english so i don't know how the english thigs are supposed to be called. i will use cursive for those)
create an example image, right click and then select Print
from the print dialog that opens then set up all the default options you want, like orientation, margins, paper type, etc etc for the specific printer you're gonna use.
Go to printer settings, under tools then edit Printer Status Monitoring
edit monitoring frequency to "only during print jobs". it should be disabled by default
in the next tab, modify polling frequency to the minimum available, 100ms during print jobs (you can use a lower one for the while not printing option
Assuming the following:
only your program is running this script
theres always only 1 printing job at a time for a given printer
the printer drivers were not written by a monkey and they actually report the current, correct printer status
This little hack will allow to print an image from a command and await job completion, with error management; and uses only windows preinstalled software
Further optimization could be done by keeping powershell subprocess active and only passing it scripts in the & "path\to\print.ps1" "C:\absolute\path\to\file.png" format, waiting for standard output to report an OK or a KO; but only if mass printing is required.
Having had to work on this again, just wanted to add a simpler solution in "pure" python using the pywin32 package
import time
import subprocess
from typing import List
try:
import win32print as wprint
PRINTERS: List[str] = [p[2] for p in wprint.EnumPrinters(wprint.PRINTER_ENUM_LOCAL)]
PRINTER_DEFAULT = wprint.GetDefaultPrinter()
WIN32_SUPPORTED = True
except:
print("[!!] an error occured while retrieving printers")
# you could throw an exception or whatever
# bla bla do other stuff
if "WIN32_SUPPORTED" in globals():
__printImg_win32(file, printer_name)
def __printImg_win32(file: str, printer: str = ""):
if not printer:
printer = PRINTER_DEFAULT
# verify prerequisites here
# i still do prefer to print calling rundll32 directly,
# because of the default printer settings shenaningans
# and also because i've reliably used it to spool millions of jobs
subprocess.check_call(
[
"C:\\Windows\\System32\\rundll32",
"C:\\Windows\\System32\\shimgvw.dll,ImageView_PrintTo",
file,
printer,
]
)
__monitorJob_win32(printer)
pass
def __monitorJob_win32(printer: str, timeout=16.0):
p = wprint.OpenPrinter(printer)
# wait for job to be sheduled
t0 = time.time()
while (time.time()-t0) < timeout:
ptrr = wprint.GetPrinter(p, 2)
# unsure about those flags, but definitively not errors.
# it seems they are "moving paper forward"
if ptrr["Status"] != 0 and ptrr["Status"] not in [1024,1048576]:
raise Error("Printer is in error (status %d)!" % ptrr["Status"])
if ptrr["cJobs"] > 0:
break
time.sleep(0.1)
else:
raise Error("Printer timeout sheduling job!")
# await job completion
t0 = time.time()
while (time.time()-t0) < timeout:
ptrr = wprint.GetPrinter(p, 2)
if ptrr["Status"] != 0 and ptrr["Status"] not in [1024,1048576]:
raise Error("Printer is in error (status %d)!" % ptrr["Status"])
if ptrr["cJobs"] == 0 and ptrr["Status"] == 0:
break
time.sleep(0.1)
else:
raise Error("Printer timeout waiting for completion!")
wprint.ClosePrinter(p)
return
useful additional resources
Print image files using python
Catch events from printer in Windows
pywin32's win32print "documentation"

Windows Perl - Win32::Systray and IO::Socket::Multicast - how to get them work together?

Hi stackoverflow community!
I am trying to make Win32::Systray and IO::Socket::Multicast work together in one script. But I just can't for a week haha!
1st target: make a system tray with buttons (Options/Exit/Other stuff).
2nd target: listen for port communication and execute actions once any communication received.
The problem: Once one of them will execute (like the Win32::Systray), the script loops on either the systray or the port listener (like a loop, I did not review the module itself - lazy).
For the Muticast too, its on loop, that is the end of it. It wont continue to below of the script (obviously its a endless loop, it will loop there).
What I need: I want to make a systray icon that will fuction properly when I click it (showing buttons) and in the same time, listening to port communication with no conflicts on both.
#---------------------------------------------------------------
# Perl Modules
#---------------------------------------------------------------
# use strict;
# use warnings;
use IO::Socket::Multicast;
#use IO::Socket::INET;
use Win32;
use Win32::GUI;
use Win32::SysTray;
use Win32::Process;
use File::Basename;
use File::Spec::Functions qw[ catfile rel2abs updir ];
use Tk;
my $domain_cfg = rel2abs(dirname($0)).'\cfg\site_domain.cfg';
open(FH, '<', $domain_cfg) or die $!;
while(<FH>){
$domain = $_;
}
close(FH);
print "domain_file:$domain_cfg\n";
print "domain:$domain\n";
#---------------------------------------------------------------
# SysTray
#---------------------------------------------------------------
my $tray = new Win32::SysTray (
'icon' => rel2abs(dirname($0)).'\img\server.ico', #\\Demosai DIR
'single' => 1,
'name' => $title,
'tip' => 'test'
) or exit 0;
$tray->setMenu (
"> Option" => sub {
print "LOL!\n";
},
">-" => 0,
"> Exit" => sub {
terminate();
},
);
#$tray->runApplication;
$tray->runApplication unless fork;
#---------------------------------------------------------------
# Wait and Read Port
#---------------------------------------------------------------
$default_port = '3671';
$default_reply_ip = '00.00.00.00';
$default_reply_port = '3672';
$default_reply = 'MESSAGE_RECEIVED';
print "Running Comms... Port [$default_port]\n";
# Create a new multicast UDP socket ready to read datagrams sent to port 3671
my $s = IO::Socket::Multicast->new(LocalPort=>$default_port);
# Add a multicast group address
$s->mcast_add('00.00.00.00');
# Optionally, the address can be associated with a specific network adapter
# $s->mcast_add('00.00.00.00','eth0');
$tmp_ctr = 0;
my $data;
while (1) {
# Wait until a packet is received
$s->recv($data,1024);
print "Data received: ".length($data)." = $data\n";
#---------------------------------------------------------------
# REPLY
#---------------------------------------------------------------
print "Replying...";
#$s->mcast_send($default_reply,$default_reply_ip);
}
#---------------------------------------------------------------
# END OF FILE
#---------------------------------------------------------------
print "EOF\n";

Using IO::Select on STDIN on Windows

When I run the code below on a Linux system, as expected it outputs Nothing is ready about every two seconds, and also outputs anything entered on to console.
But on Windows, can_read returns instantly with zero items.
use IO::Select;
my $sel = IO::Select->new();
$sel->add(\*STDIN);
while ( 1 ) {
my #ready = $sel->can_read(2);
if ( scalar #ready == 0 ) {
print "Nothing is ready\n";
}
foreach my $fh ( #ready ) {
if ( $fh eq \*STDIN ) {
my $in = <STDIN>;
print "got $in from stdin\n";
}
}
}
It seems that select works only on Windows sockets and not on STDIN. How can I use IO::Select on STDIN on a Windows system?
You cannot, perldoc perlport states:
select Only implemented on sockets. (Win32, VMS)
This is caused by Windows itself implementing select() only for sockets, see https://learn.microsoft.com/de-de/windows/desktop/api/winsock2/nf-winsock2-select.
The Windows equivalent seems to be I/O Completion Ports. But you have to find a way to use them from Perl.
If you really just care about STDIN, you can poll in a loop with Term::ReadKey with a ReadMode of -1 (non-blocking). As the name of the module suggests, this may only work on a tty.

How do I do a non-blocking read from a pipe in Perl?

I have a program which is calling another program and processing the child's output, ie:
my $pid = open($handle, "$commandPath $options |");
Now I've tried a couple different ways to read from the handle without blocking with little or no success.
I found related questions:
perl-win32-how-to-do-a-non-blocking-read-of-a-filehandle-from-another-process
why-does-my-perl-sysread-block-when-reading-from-a-socket
But they suffer from the problems:
ioctl consistently crashes perl
sysread blocks on 0 bytes (a common occurrence)
I'm not sure how to go about solving this problem.
Pipes are not as functional on Windows as they are on Unix-y systems. You can't use the 4-argument select on them and the default capacity is miniscule.
You are better off trying a socket or file based workaround.
$pid = fork();
if (defined($pid) && $pid == 0) {
exit system("$commandPath $options > $someTemporaryFile");
}
open($handle, "<$someTemporaryFile");
Now you have a couple more cans of worms to deal with -- running waitpid periodically to check when the background process has stopped creating output, calling seek $handle,0,1 to clear the eof condition after you read from $handle, cleaning up the temporary file, but it works.
I have written the Forks::Super module to deal with issues like this (and many others). For this problem you would use it like
use Forks::Super;
my $pid = fork { cmd => "$commandPath $options", child_fh => "out" };
my $job = Forks::Super::Job::get($pid);
while (!$job->is_complete) {
#someInputToProcess = $job->read_stdout();
... process input ...
... optional sleep here so you don't consume CPU waiting for input ...
}
waitpid $pid, 0;
#theLastInputToProcess = $job->read_stdout();

How can I get forking pipes to work in Perl on Windows?

I'm trying to port a Perl script over from Unix to Windows but am having a near impossible time getting it to work due to the unsupported forking pipes in the open function. Here's the code:
sub p4_get_file_content {
my $filespec = shift;
return 'Content placeholder!' if ($options{'dry-run'});
debug("p4_get_file_content: $filespec\n");
local *P4_OUTPUT;
local $/ = undef;
my $pid = open(P4_OUTPUT, "-|");
die "Fork failed: $!" unless defined $pid;
if ($pid == 0) { # child
my $p4 = p4_init();
my $result = undef;
$result = $p4->Run('print', $filespec);
die $p4->Errors() if $p4->ErrorCount();
if (ref $result eq 'ARRAY') {
for (my $i = 1; $i < #$result; $i++) {
print $result->[$i];
}
}
$p4->Disconnect();
exit 0;
}
my $content = <P4_OUTPUT>;
close(P4_OUTPUT) or die "Close failed: ($?) $!";
return $content;
}
The error is:
'-' is not recognized as an internal or external command,
operable program or batch file.
Does anyone know how to make this work? Thanks!
Mike
I know it's not a direct answer to your question, but it looks like you're scripting something on top of Perforce in Perl? If so you might find an existing library does what you want already and save yourself a lot of headaches, or at least give you some sample code to work from.
For example:
P4Perl
P4::Server
P4::C4
EDIT: Now that I know what you're doing I'm guessing you're trying to port p42svn to Windows, or rather make it compatible with Windows at least. See this thread for a discussion of this exact issue. The recommendation (untested) is to try the code samples listed at http://perldoc.perl.org/perlfork.html under "Forking pipe open() not yet implemented" to explicitly create the pipe instead.
It's not going to work as-is. You'll need to find another method to accomplish what it's doing. It doesn't look like there's that burning a need for the fork-pipe, but it's hard to tell since I don't know what a p4 is and a lot of your code is being lost to angle bracket interpretation.

Resources