macdev has asked for the wisdom of the Perl Monks concerning the following question:

I can write to, and read from, a TRENDnet TU-S9 USB to RS-232 Adapter via AppleScript and Python; not with Perl.
I cannot figure what I maybe missing or configured improperly.
Any Assistance is welcomed and appreciated.

#!/usr/local/bin/perl use strict; # Computer: iMac 2017;, USB to Serial Adapter: TRENDnet TU-S9; # Just showing the Locations of the 'SerialPort' Files. #use lib "/usr/local/lib/perl5/site_perl/5.36.0/darwin-2level/auto/Dev +ice/SerialPort/SerialPort.bundle"; #use lib "/usr/local/lib/perl5/site_perl/5.36.0/darwin-2level/Device/S +erialPort/SerialPort.pm"; use Device::SerialPort; # I tried each of the below my $port = Device::SericalPort->new ...Lin +es of Code. #my $port = Device::SerialPort->new("/dev/cu.usbserial"); #my $port = Device::SerialPort->new("/dev/tty.usbserial"); #my $port = Device::SerialPort->new("/dev/ttys0"); my $port = Device::SerialPort->new("/dev/ttys1"); # Like the three (3 +) Lines above, this Driver produces the same Result. # The below Parameters are from my AppleScript and Python Code , which + both work flawlessly via the TRENDnet TU-S9. $port->databits(8); $port->baudrate(4800); $port->handshake("rts"); $port->parity("none"); $port->stopbits(1); #$port->write_settings || undef $port; print "01"; print "\n"; $port->write("FA21050000;"); # Write "FA21050000;" Hz to Yaesu FTdx-1 +200 Transceiver to set VFO-A Frequency. # The VFO-A Frequency is NOT changed. print "02"; print "\n"; $port->write("FB;"); # Write "FB;" to FTdx-1200 to have Trans +ceiver place Frequency of VFO-B into its Buffer. my $byte=$port->read(11); # Read the VFO-B Frequency, in "FBXXXXXX +XX;" Format print "$byte"; # Print expected returned Frequency, in +"FBXXXXXXXX;" Format, where XXXXXXXX is Freq. in Hz. print "\n"; #The VFO-B Frequency is NOT retreived from the FTdx-1200 Transceiver. print "03"; print "\n"; $port->close();

Instead of seeing: 01 ... followed by the, Variable Frequency Oscillator, VFO-A change to '21050000' Hz- which does not occur.
02
FBXXXXXXXX; ... where 'XXXXXXXX' is the Frequency, in Hz, which is what ever VFO-B is set to.
03
... what is displayed is:
01 ... VFO-A is not changed to '21050000' Hz.
02

03

Replies are listed 'Best First'.
Re: Cannot write to, or read from, the SerialPort
by haukex (Archbishop) on Jun 15, 2022 at 05:44 UTC

    Always Use strict and warnings!

    # I tried each of the below my $port = Device::SericalPort->new ...Lin +es of Code. #my $port = Device::SerialPort->new("/dev/cu.usbserial"); #my $port = Device::SerialPort->new("/dev/tty.usbserial"); #my $port = Device::SerialPort->new("/dev/ttys0");

    You should use the same device name that works in your other scripts.

    #$port->write_settings || undef $port;

    Why did you commment this out? Try uncommenting this.

    Although I've used Device::SerialPort successfully in the past, I now strongly recommend IO::Termios on *NIX systems instead - see my post Lower-Level Serial Port Access on *NIX.

      01. Thank you for viewing the Code and replying.

      02. In my Python Code the following is used: 'lSer=serial.Serial('/dev/cu.usbserial', 4800)' is used.

      With respect to the following Lines of Code ...
      my $port = Device::SerialPort->new("/dev/cu.usbserial"); my $port = Device::SerialPort->new("/dev/tty.usbserial"); my $port = Device::SerialPort->new("/dev/ttys0"); my $port = Device::SerialPort->new("/dev/ttys1");
      Thus, the need for the commented out Lines of Code, of the unwanted Lines. I was trying any Driver I thought may work.

      03. Viewing various Perl Code Examples ...
      $port->write_settings || undef $port;
      ... was not in some of the Example Code. Thus, I tried my Perl Code with / without 'port->write_settings' used.
      I just happened to comment out that Line of Code, on my last Test, before posting my Request.

      I will make sure in any new Code, to use: $port->write_settings || undef $port;

      04. I am looking into the 'IO::Termios' Module and your 'Lower-Level Serial Port Access on *NIX' Post; thank you for the Suggestions.
Re: Cannot write to, or read from, the SerialPort
by Marshall (Canon) on Jun 15, 2022 at 19:00 UTC
    I read your post with some interest because a friend of mine has requested a related project which I think could be done in Perl but I'm a bit fuzzy on the details.

    From reading the spec for Device::SerialPort and looking at the CAT manual for the FTDX-1200, I figure that haukex has it right -> all you need to do is write_settings to send all of the device settings to the device before starting to use the serial port. Evidently statements like $port->parity("none"); set some parameter in a data structure, but the method does not actually send that info onto the actual device.

    For testing, I would put a sleep(x seconds) after: $port->write("FA21050000;");. If the VFO-A command doesn't work, the fancier command for VFO-B probably won't work either! I didn't see any timing restrictions in the CAT manual, but for testing, I would pause and just look at the radio display to see what, if anything, happens on the display before immediately sending (or trying to send) another command to the radio.

    I don't know the reliability that you expect from your application. You are controlling a piece of hardware. Hardware makes mistakes for a variety of reasons. The idea of send command, e.g. "FB;" and then wait with a read() until exactly 11 bytes have been returned, will work virtually all of the time, but not all of the time. This detail may not matter for your application. But best practice would be to "timeout" a blocking input request like that so that your program does not "hang". A perhaps contrived example: radio is powered off after 5 of 11 bytes have been sent to you. Radio is powered back on. It will not send anymore bytes, but you will still be waiting to read some bytes which will never come. In that example, what is the probability of this happening? Very, very low. But, there are other reasons why this same sort of thing could happen. In general, if it can happen, it will happen. It is just a matter of "how often".

    I am curious as to your actual application and whether or not you have to co-exist with some other application?

    My friend has two apps which want to talk to the same radio serial port. You can't just willy-nilly "y" the cables from 2 different serial ports together. That actually will work a lot of the time, but not all of time! What is desired is a "man in the middle" box. This box talks to the single "real serial port", but looks like two separate virtual ports to 2 different applications. If App A sends "FB;", it gets the response to that command before sequencing in perhaps an "FA;" command from App B. This man-in-the-middle is the "traffic cop" so that the radio only "hears one single voice" and is completely unaware that there are two end user apps out there.

    I see how to to talk to the USB->Serial Port. But I am unsure how to make my software look like a serial port (COM port) to other applications?
    My traffic cop app will be Windows based.

      01. Thank you for viewing the Code and replying.

      02. I will be looking into the 'IO::Termios' Module as suggested by 'haukex'.

      03. Viewing various Perl Code Examples ...

      $port->write_settings || undef $port;

      ... was not in some of the Example Code. Thus, I tried my Perl Code with / without 'port->write_settings' used.
      I just happened to comment out that Line of Code, on my last Test, before posting my Request.

      I will make sure in any new Code, to use: $port->write_settings || undef $port;

      04. I beleived I did use 'sleep()' in previous Versions of the Code - before the posted Version - but I will again try using 'sleep()' after each '$port->write("...")'. Thank you for the Suggestion.

      05. "I am curious as to your actual application and whether or not you have to co-exist with some other application?" With repect to Python - No. I have wriiten well over 100 '.py' Script Files for the many Amateur Radio Contests which happen over the Year. My Scripts may call some personally written Modules for commonly used Routines - such as the Sample Python below; but the Scripts are not reliant on any external Application(s).

      Please see '06.' about 'co-exist with some other application(s)'.

      06. "My friend has two apps which want to talk to the same radio serial port ..." - correct, with respect to possible Conflicts.
      My Python Code closes the Serial Port once an Action is performed. However, once I had 'FLDIGI' running, using 'FLRIG' as its Frequency Controller. I then executed an AppleScript Applet to change the Power Output of my Transceiver - and it complained. Once I quitted 'FLDIGI' and 'FLRIG', the AppleScript worked just find. Either one or both - 'FLDIGI' and / or 'FLRIG' keep sampling the Serial Port; thus, it was never closed.
      The same would happen if I tried to run a Python Script, requiring access to the Serial Port - if 'FLDIGI' and "FLRIG' were running.

      07. I do thank you for the Suggestions.


      Below is only one (1) of many Python Functions / Methods / Procedures I use for accessing Yaesu Transceivers - this may assist you in your Perl Version for your Friend.

      ------------------------------------------------------------ Python Function / Method / Procedure for obtaining the VFO-A Frequency, in Hz; the Mode - 'LSB', 'USB', 'CW', 'FM', 'AM', 'RTTY-LSB', 'CW-R', 'DATA-LSB', 'RTTY-USB', '----', 'FM-N', or 'DATA-USB'; and Output Power, in Watts, of the Transceiver

      ----

      def Handle_FTdx1200(): lSer=serial.Serial('/dev/cu.usbserial', 4800) # Open a Serial Port, and configure its Baud Rate. lSer.write('FA;'.encode()) # Tell FTdx-1200 to obtain 'FA' VFO-A Frequency. lFreq=lSer.read(11) # Retrieve the 'FA' Data lSer.flush() # Purge the Input Buffer of any Characters. lSer.write('MD0;'.encode()) # Tell FTdx-1200 to obtain 'MD' Module. lMode=lSer.read(5) # Retrieve the 'MD' Data lSer.flush() # Purge the Input Buffer of any Characters. lSer.write('PC;'.encode()) # Tell FTdx-1200 to obtain 'PC' lPower Output Value. lPower=lSer.read(6) # Retrieve the 'PC' Data lSer.close() # Close the Connection of the USB Serial Adapter. lFreq=lFreq[2:10] # Remove 'FA' and ending ';' lFreq=lFreq.decode("utf-8") # Without '.decode("utf-8")' 'lFreq' would be printed, #in the print Line below # "b'XXX', where XXX is the Value of lFreq. lPower=lPower[2:5] # Remove 'PC' and ending ';' #lPower=str(int(lPower)) # If 'lPower' is less than 100 a preceding '0' will appear. #This Code removes the preceding '0'. lPower=lPower.decode("utf-8") lMode=lMode[3:4] lMode=lMode.decode("utf-8") # Without '.decode("utf-8")' 'lMode' would be printed, #in the print Line below as "b'XXX', where XXX is the Value of lMode. if((lMode=='3') or (lMode=='7')): lMode='CW' elif((lMode=='1') or (lMode=='2')): lMode='PH' elif(lMode=='5'): lMode='AM' elif((lMode=='4') or (lMode=='B')): lMode='FM' elif((lMode=='8') or (lMode=='C')): lMode='DG' elif((lMode=='6') or (lMode=='9')): lMode='RY' else: lMode='---' return(lFreq, lMode, lPower)
      ----

      The above Code works flawlessly at the FTdx-1200 default Baud Rate of 4800; as well at - 9600, 19200, and 38400 bps.

      ------------------------------------------------------------
Re: Cannot write to, or read from, the SerialPort
by macdev (Initiate) on Jun 16, 2022 at 20:56 UTC
    01. Simply put - Thank you haukex and Marshall.

    02. Yes, adding a 'sleep()' Command below each '$port->write*()' solved my Dilemma. However, I used 'Time::HiRes' - usec() Function for delays less than 1 Second.

    I also see the usefulness of the ''IO::Termios' Module' Functions.

    Below is a complete Code Example:
    #!/usr/bin/perl #use strict; #use warnings; use Device::SerialPort; use Time::HiRes qw(usleep); sub Handle_FTdx1200{ my $delayValue=100000; my $port = Device::SerialPort->new("/dev/cu.usbserial"); $port->databits(8); $port->baudrate(4800); $port->handshake("rts"); $port->parity("none"); $port->stopbits(1); $port->write_settings || undef $port; $port->write("FA;"); usleep($delayValue); my $VFOA=$port->read(11); $port->write("MD0;"); usleep($delayValue); my $Mode=$port->read(5); $port->write("PC;"); usleep($delayValue); my $Pwr=$port->read(6); $port->close(); $VFOA=substr($VFOA, 2, 8); $Mode=substr($Mode, 3, 1); $Pwr=substr($Pwr, 2, 3); $Pwr=int($Pwr); $Pwr="$Pwr"; return($VFOA, $Mode, $Pwr); } my ($tFreq, $tMode, $tPower)=Handle_FTdx1200(); print($tFreq); print("\n"); print($tMode); print("\n"); print($tPower); print("\n");
    03. Apparently, in my initial Perl Code, I did not try the 'sleep()' Function.
    It is in AppleScript that a delay Function, after any 'serialport Function, is required.

    Below is AppleScript Code to set the Keyer Speed to 38 Words per Minute.
    set desiredPort to "/dev/cu.usbserial" set tSpeed to "38" try set tPort to serialport open desiredPort bps rate 4800 data bits 8 + parity 0 stop bits 1 handshake 3 delay 0.1 serialport write ("KS;") to tPort delay 0.1 set originalSpeed to serialport read tPort delay 0.1 set originalSpeed to (get (characters 4 through 5 of originalSpeed +) as string) as integer serialport write ("KS0" & tSpeed & ";") to tPort delay 0.1 serialport write ("KS;") to tPort delay 0.1 set currentSpeed to serialport read tPort delay 0.1 set currentSpeed to (get (characters 4 through 5 of currentSpeed) +as string) as integer display dialog "Original Speed: " & originalSpeed & return & "Curr +ent Speed: " & currentSpeed giving up after 5 buttons ("OK") default +button "OK" serialport close tPort on error serialport close tPort end try
    In conclusion - again, thank you - to the both of you.
      I am also glad that you got this thing working to your satisfaction! Normally, I would think that these extra delays would not be necessary. I suggested that to simplify the situation (which is always a good idea for debugging). The delay that you have gives enough time for the command to be sent and for the radio to have already decided on its response. I suspect that there is something wrong with the hardware flow control handshake. What exactly is wrong is very configuration specific - could even be a hardware defect in your USB-Serial adaptor gizmo.

      I run the CAT I/F at 38400 baud to talk to a K3. A normal contest logger will keep a steady stream of commands: what freq and mode are you on? to the radio - multiple times per second. This is "background polling noise" that is continuous. When I have the spectrum display attached, I get what is called a "waterfall" display of signal strengths over a range of frequencies. With one logger, I have macros like "100" or "20" that I type in. The logger sequences my macro's radio commands in between the normal polling so that there are not conflicts. With say the "100" command, normally the frequency display will be set up with my current frequency in the middle and I will see 50 KHz on either side. However, if say I am near the lower band edge, the display will show the lower band edge on the left and show 100 KHz up from that edge with my frequency shown wherever it happens to be. Some loggers do not allow me to write my own radio macros and those commands would come from a distinct, separate application. Therefore the need for this "man-in-the-middle" box to do the command sequencing to the radio. If my friend really wants this, I may have to write it in C. Or I suppose I could build a separate hardware box with an Arduino processor as a hardware solution.

      In general, using fixed delays after some hardware (or software) command is a bad idea. However, your situation is simple enough that I wouldn't worry about it.