2

I've been struggling lately with the following in Python: I need to interact with a 3rd party program (an executable) which needs some arguments for controlling external HW. I'm able to communicate with this exe through subprocess functions passing arguments as expected by the exe. However, when running some tests, due to fail in the test results, I could see it was related to the fact that I'm passing all the arguments too quickly to the exe whereas it needs some time to process it in between args. I'm not sure my explanations are clear enough, I'll post some examples. I need to pass the type of the HW, its port COM and some other informations. This is the prompt from the exe:

Enter reader type: 2 Enter COM port (e.g. COM3): COM3

After the COM port argument is passed, it can take up to 3-8 seconds to activate the HW correctly. Then I need to pass other arguments. The problem is when I send all the arguments at once, the HW responds sometimes as busy and don't take all the arguments. Ideally, I should add a delay after passing the port COM argument but didn't manage to do it. This is where I would need some help, please.

Here is the code I'm using in the main :

_, op, er = _run_cisc_command_timeout([executable],
                                      [reader + "\n", com + "\n", amount + "\n", timeout + "\n", "-1\n", "a\n"],1000)
        value = "".join(op)

and this is the function dealing with the HW:

p = subprocess.Popen(_args, shell=withShell,
                     stdin=subprocess.PIPE, stdout=subprocess.PIPE)

if debug:
    for v in input:
        print("DEBUG: {}".format(v))
        p.stdin.write(bytes(v, 'utf-8'))
else:
    [p.stdin.write(bytes(v, 'utf-8')) for v in input]
    
timer = Timer(timeout, p.kill)
try:
    timer.start()
    stdout, stderr = p.communicate()
    if debug:
        print("DEBUG: {}".format(stdout))
    result[index] = [p.returncode, stdout, stderr]
finally:
    timer.cancel()
return True

Thanks in advance for your help/suggestions/ideas on how to process this.

[EDIT] So I tried with pexpect the following:

p = subprocess.Popen(tool, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
o = pexpect.fdpexpect.fdspawn(p.stdout.fileno())
index = o.expect(["Enter*",pexpect.TIMEOUT])

it get stucks at o.expect line as if the stdout was empty or not read ?

strong text[EDIT based on Rob answers]: here is the output when passing 'a' to the child:

a

Press any key to continue . . . a

Library Test Tool Enter reader type: Enter COM port (e.g. COM3): 2 - UNKNOWN: Reader Control Library 2 - UNKNOWN: Library 2 - UNKNOWN: Version 1.6.0.0 2 - UNKNOWN: 0 - UNKNOWN: Intialize Reader - Error: unknown reader type Failed to initialize reader - shutting down Failed to initialize reader - shutting down Traceback (most recent call last): File "C:/Data/latest_v3/run_cisc_tool.py", line 229, in cisc_pexpect_2(exec) File "C:/Data/latest_v3/run_cisc_tool.py", line 173, in cisc_pexpect_2 child.expect(["Press*", ], timeout=10) File "C:\python-3.6.8.amd64\lib\site-packages\pexpect\spawnbase.py", line 344, in expect timeout, searchwindowsize, async_) File "C:\python-3.6.8.amd64\lib\site-packages\pexpect\spawnbase.py", line 372, in expect_list return exp.expect_loop(timeout) File "C:\python-3.6.8.amd64\lib\site-packages\pexpect\expect.py", line 179, in expect_loop return self.eof(e) File "C:\python-3.6.8.amd64\lib\site-packages\pexpect\expect.py", line 122, in eof raise exc pexpect.exceptions.EOF: End Of File (EOF). <pexpect.popen_spawn.PopenSpawn object at 0x0000028A3EB02978> searcher: searcher_re: 0: re.compile(b'Press*')

and when passing correct arguments to initiate the HW I simply get the error message for the pexpect's timeout.

marco38
  • 21
  • 2
  • did you try to use `time.sleep` between passing arguments? – vinzenz Jan 05 '22 at 14:01
  • Take a look on this: https://stackoverflow.com/questions/54319960/wait-for-a-prompt-from-a-subprocess-before-sending-stdin-input Looks like it is close to your needs. – IgorZ Jan 05 '22 at 14:05
  • @vinzBad: yes, this was the first thing I tried, but unsuccessfully ! – marco38 Jan 05 '22 at 14:21
  • @IgorZ: thanks for the suggestion, but it doesn't seem to work (see above, I have edited my post). – marco38 Jan 05 '22 at 15:56

1 Answers1

0

I am making some assumptions here, so correct me if I am wrong, and I will adjust my answer:

  1. The 3rd party executable that controls the hardware is interactive, and it requests arguments, such as Reader Type and COM port, as needed; that is, you cannot send all the arguments in one command line string like this: ping -l 64 -n 4 8.8.8.8.
  2. Based on your [EDIT], most of the prompts start with "Enter", as in Enter COM port:.
  3. Since you are using COM and pexpect.popen_spawn.PopenSpawn, instead of /dev/ttyS* or pexpect.spawn, I tested this using Windows (see https://pexpect.readthedocs.io/en/stable/overview.html#windows).

I don't recognize your executable, so try this out, and customize it for your executable:

NOTE - Tested on Windows 11, using Python 3.9

Automation Script:

"""temporize.py"""

import sys

import pexpect
from pexpect import popen_spawn

debug = True

# Your executable would replace the spawn argument: i.e., PopenSpawn([executable]])
child = pexpect.popen_spawn.PopenSpawn("python temporize_child.py")
if debug:
    # Send input and output to screen. Use logfile_read to see output only
    child.logfile = sys.stdout.buffer

args = [2, "COM3", 100, 60, -1, "a", ]

for a in args:
    child.expect(["Enter*", "Input*", ], timeout=60)
    child.sendline(str(a))

child.expect(pexpect.EOF)

print("Script complete. Have a nice day.")

"Executable":

"""temporize_child.py"""

import time

reader_type = input("Enter reader type: ")
com_port = input("Enter COM port: ")

# I added a delay, to simulate your delay
print("\n(Simulated 3-8 second delay to activate the HW correctly...)\n")
time.sleep(8)

amount = input("Enter the amount: ")
print(f"Amount set to {amount}.")
timeout = input("Input the timeout: ")
print(f"Timeout set to {timeout}.")
arg5 = input("Enter Argument 5: ")
print(f"Argument 5 set to {arg5}.")
arg6 = input("Input Argument 6: ")
print(f"Argument 6 set to {arg6}.")

Output:

Enter reader type: 2
Enter COM port: COM3

(Simulated 3-8 second delay to activate the HW correctly...)

Enter the amount: 100
Amount set to 100.
Input the timeout: 60
Timeout set to 60.
Enter Argument 5: -1
Argument 5 set to -1.
Input Argument 6: a
Argument 6 set to a.
Script complete. Have a nice day.

WARNNG: To account for different prompts, I used a list with Enter* and Input*, but if you have too many different prompts, you may not be able to use a loop.

Also, watch your line endings. sendline adds a newline (\n), but some applications and devices, such as with Cisco, require a carriage return (\r) as well to complete the CRLF. Not including the CR may screw up the timing of your inputs (e.g., the script will enter an empty newline at the COM port prompt).

Rob G
  • 673
  • 3
  • 10
  • Hi Rob, thanks a lot for your answer. Your assumptions were correct, I'm using WIN environment. I tried as suggested but the child process exists in timeout at the line child.expect()... It seems like there is nothing outputted in the pipe ? I tried with a .py instead of the .exe and it works like a charm. Seems that the popen doesn't work ? – marco38 Jan 10 '22 at 09:46
  • ok, I tried the following: child.sendline('a') child.sendline('a') child.expect(["Press*", ], timeout=10) the exe respond but it can't connect to the HW because the data is rubbished. If I send expected data to connect to it, it just doesn't answer... child.sendline(str(args[0])) child.sendline(str(args[1])) child.expect(["Press*", ], timeout=10) Something wrong in the way I send the commands ? but I don't understand what it could be. any ideas ? – marco38 Jan 10 '22 at 14:36
  • You may have to add a carriage return to complete the CRLF (i.e., `child.sendline(str(args[0]) + '\r')`). What is the logfile output? – Rob G Jan 10 '22 at 18:19
  • Yes, I tried to add the carriage return, sorry I forgot to mention it in my previous comment. It didn't work. I will edit my question to provide the output logs as they are too long to put put in this comment. – marco38 Jan 11 '22 at 09:02
  • Ah-ha! Looks like extra characters in the buffer. When the program asks, "Press any key to continue...", you `sendline('a')`, which is actually `'a' + '\n'`. Therefore, `Enter reader type:` gets the extra newline, and `Enter COM port (e.g. COM3):` gets the `2` that should have gone to reader type. Try using `send()` or `send('\r')` instead of `sendline()` for "Press any key to continue..." – Rob G Jan 11 '22 at 21:20
  • no, still same issue... :( I'll keep looking. – marco38 Jan 13 '22 at 14:20