Cookies op Tweakers

Tweakers is onderdeel van DPG Media en maakt gebruik van cookies, JavaScript en vergelijkbare technologie om je onder andere een optimale gebruikerservaring te bieden. Ook kan Tweakers hierdoor het gedrag van bezoekers vastleggen en analyseren. Door gebruik te maken van deze website, of door op 'Cookies accepteren' te klikken, geef je toestemming voor het gebruik van cookies. Wil je meer informatie over cookies en hoe ze worden gebruikt? Bekijk dan ons cookiebeleid.

Meer informatie
Toon posts:

[Python] Voer command uit en controleer uitkomst op string

Pagina: 1
Acties:

Vraag


Anoniem: 1277286

Topicstarter
Mijn vraag
Ik heb een Python script dat een command uitvoert m.b.v. subprocess.run(). Deze command heeft een uitkomst, maar die wordt niet meteen "gemaakt". Er is een onbekende tijd tussen waneer er een reactie/uitkomt is. Wat ik nu wil, is dat er na het uitvoeren van het commando, telkens gecheckt wordt of er al een reactie is. Als dit het geval is, dan wil ik deze reactie controleren om te kijken of er een specifieke string in zit. Als deze string aanwezig is, dan wil ik het subprocess sluiten en een andere normale Python functie uitvoeren.

Relevante software en hardware die ik gebruik
Python3 op Ubuntu 16.04 met eric6 Python IDE

Wat ik al gevonden of geprobeerd heb
Ik heb al gekeken naar subprocess.check_output(), of de uitkomst van het command decoden om vervolgens de string eruit te halen. Echter loop ik (volgens mij) telkens vast op het feit dat er geen onmiddelijke reactie is op het command.

Aanvullende informatie
Dit is het script wat ik nu heb. Hiermee krijg ik al wel de gewenste output op mijn scherm. Echter lukt het me niet om de gewenste vervolgstappen te halen.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import subprocess
import sys

def next_step():
  print("Navigation goal reached!")

def check_status():
  command = "rostopic echo move_base/result/status/text"
  task = subprocess.run(command, shell=True, stderr=subprocess.PIPE)
  while(True):
    out = task.stderr.read(1)
    if out == '' and task.poll() != None:
      break
    elif out != '':
      sys.stdout.write(out)
      sys.stdout.flush()

if __name__ == "__main__":
  check_status()


De output van het command is na X seconden (nogmaals, deze periode is onbekend en kan variëren van 3 seconden tot enkele minuten):
code:
1
2
"Goal reached."
---

Nu wil ik in mijn code dus controleren of de string "Goal reached" aanwezig is in de output. Zo ja, dan wil ik uit de hele next_step() functie stappen (en alles wat hierin staat sluiten) en doorgaan naar de next_step() functie.

Hopelijk heb ik genoeg informatie gegeven en is er iemand die mij kan helpen :)

Beste antwoord (via Anoniem: 1277286 op 03-06-2020 19:21)


  • CCJJVV
  • Registratie: maart 2016
  • Laatst online: 03:29
Even een vraagje vooraf, ben je zelf bezig met ROS of is dit een bestaand iets dat je wilt uitbreiden?

Dit is namelijk niet de manier waarop binnen ROS naar een topic geluisterd moet worden. Het kan weliswaar door het commando rostopic echo $_topic_name te gebruiken alleen is dit commando eigenlijk bedoeld om makkelijk te debuggen.

Als ik jou was dan zou ik de volgende tutorial even doorlezen: http://wiki.ros.org/ROS/T...herSubscriber%28python%29. En dan in het bijzonder de subscriber node die er als volgt uit ziet:


code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import rospy
from std_msgs.msg import String

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
    
def listener():

    rospy.init_node('listener', anonymous=True)
    rospy.Subscriber("chatter", String, callback) 
    rospy.spin()

if __name__ == '__main__':
    listener()


In de callback functie krijg je in principe terug wat jij wilt hebben.
Elke keer dat er een message op het "chatter" topic wordt gepubliceerd wordt de callback functie aangeroepen.

De oplossing die je nu probeert uit te werken is nogal een anti-pattern. Het feit dat het je er moeite mee hebt zegt eigenlijk al dat het makkelijker kan.

[Voor 11% gewijzigd door CCJJVV op 03-06-2020 18:47]

Alle reacties


Acties:
  • +1Henk 'm!

  • ValHallASW
  • Registratie: februari 2003
  • Niet online
Kijk even naar de documentatie van subprocess.run: https://docs.python.org/3...ocess.html#subprocess.run
Run the command described by args. Wait for command to complete, then return a CompletedProcess instance.

[...]

If capture_output is true, stdout and stderr will be captured. When used, the internal Popen object is automatically created with stdout=PIPE and stderr=PIPE. The stdout and stderr arguments may not be supplied at the same time as capture_output. If you wish to capture and combine both streams into one, use stdout=PIPE and stderr=STDOUT instead of capture_output.
Dus:
1) Je loop is overbodig. Op het moment dat .run() returnt is het proces al klaar
2) je moet zorgen dat je zowel stdout als stderr gecaptured worden

Probeer run() eens op bovenstaande manier te draaien, en probeer dan iets als
code:
1
2
print("stdout: ", task.stdout.read())
print("stderr: ", task.stderr.read())


waarbij read() géén parameter krijgt (je wilt immers alles lezen, niet maar één byte!)

Houd er ook rekening mee dat stdout/stderr standaard bytes teruggeven en geen string:
If encoding or errors are specified, or text is true, file objects for stdin, stdout and stderr are opened in text mode using the specified encoding and errors or the io.TextIOWrapper default. The universal_newlines argument is equivalent to text and is provided for backwards compatibility. By default, file objects are opened in binary mode.

Anoniem: 1277286

Topicstarter
ValHallASW schreef op woensdag 3 juni 2020 @ 17:29:
Kijk even naar de documentatie van subprocess.run: https://docs.python.org/3...ocess.html#subprocess.run


[...]


Dus:
1) Je loop is overbodig. Op het moment dat .run() returnt is het proces al klaar
2) je moet zorgen dat je zowel stdout als stderr gecaptured worden

Probeer run() eens op bovenstaande manier te draaien, en probeer dan iets als
code:
1
2
print("stdout: ", task.stdout.read())
print("stderr: ", task.stderr.read())


waarbij read() géén parameter krijgt (je wilt immers alles lezen, niet maar één byte!)

Houd er ook rekening mee dat stdout/stderr standaard bytes teruggeven en geen string:

[...]
Bedankt voor je reactie!

Als ik mijn script aanpas a.d.h.v. jouw feedback, dan ziet het er als volgt uit:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
import subprocess

def next_step():
  print("Navigation goal reached!")

def check_status():
  command = "rostopic echo move_base/result/status/text"
  task = subprocess.run(command, capture_output=True, shell=True)
  print("stdout: ", task.stdout.read())
  print("stderr: ", task.stderr.read())

if __name__ == "__main__":
  check_status()


Hierbij komt er niets in de terminal te staan, terwijl als ik handmatig in een terminal het command "rostopic echo move_base/result/status/text invoer, dan krijg ik wel de verwachte uitkomst.

Verder heb ik de volgende twee varianten geprobeerd met decoding:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
import subprocess

def next_step():
  print("Navigation goal reached!")

def check_status():
  command = "rostopic echo move_base/result/status/text"
  task = subprocess.run(command, capture_output=True, shell=True)
  print("stdout: ", task.stdout.read().decode("utf-8"))
  print("stderr: ", task.stderr.read().decode("utf-8"))

if __name__ == "__main__":
  check_status()

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
import subprocess

def next_step():
  print("Navigation goal reached!")

def check_status():
  command = "rostopic echo move_base/result/status/text"
  task = subprocess.run(command, capture_output=True, shell=True)
  print("stdout: ", str(task.stdout.read().decode("utf-8")))
  print("stderr: ", str(task.stderr.read().decode("utf-8")))

if __name__ == "__main__":
  check_status()

Helaas ook zonder succes...

Acties:
  • +1Henk 'm!

  • ValHallASW
  • Registratie: februari 2003
  • Niet online
Vreemd dat je geen enkele output krijgt. Ik heb het even lokaal geprobeerd:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> def check_status():
...   command = "rostopic echo move_base/result/status/text"
...   task = subprocess.run(command, capture_output=True, shell=True)
...   print("stdout: ", str(task.stdout.read().decode("utf-8")))
...   print("stderr: ", str(task.stderr.read().decode("utf-8")))
...
>>> check_status()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in check_status
AttributeError: 'bytes' object has no attribute 'read'


In andere woorden, stdout/stderr zijn blijkbaar geen streams maar bytestrings. Dat is overigens ook consistent met de documentatie zie ik nu.

Allereerst zou ik dus even uitzoeken waarom je die foutmelding niet krijgt: anders is het erg lastig het geheel te debuggen.

Ik krijg in ieder geval in task.stderr gewoon resultaat (weliswaar ''rostopic' is not recognized as an internal or external command', maar dat maakt niet uit voor het Python-gedeelte ;-))

Acties:
  • Beste antwoord
  • +1Henk 'm!

  • CCJJVV
  • Registratie: maart 2016
  • Laatst online: 03:29
Even een vraagje vooraf, ben je zelf bezig met ROS of is dit een bestaand iets dat je wilt uitbreiden?

Dit is namelijk niet de manier waarop binnen ROS naar een topic geluisterd moet worden. Het kan weliswaar door het commando rostopic echo $_topic_name te gebruiken alleen is dit commando eigenlijk bedoeld om makkelijk te debuggen.

Als ik jou was dan zou ik de volgende tutorial even doorlezen: http://wiki.ros.org/ROS/T...herSubscriber%28python%29. En dan in het bijzonder de subscriber node die er als volgt uit ziet:


code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import rospy
from std_msgs.msg import String

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
    
def listener():

    rospy.init_node('listener', anonymous=True)
    rospy.Subscriber("chatter", String, callback) 
    rospy.spin()

if __name__ == '__main__':
    listener()


In de callback functie krijg je in principe terug wat jij wilt hebben.
Elke keer dat er een message op het "chatter" topic wordt gepubliceerd wordt de callback functie aangeroepen.

De oplossing die je nu probeert uit te werken is nogal een anti-pattern. Het feit dat het je er moeite mee hebt zegt eigenlijk al dat het makkelijker kan.

[Voor 11% gewijzigd door CCJJVV op 03-06-2020 18:47]


Anoniem: 1277286

Topicstarter
CCJJVV schreef op woensdag 3 juni 2020 @ 18:41:
Even een vraagje vooraf, ben je zelf bezig met ROS of is dit een bestaand iets dat je wilt uitbreiden?

Dit is namelijk niet de manier waarop binnen ROS naar een topic geluisterd moet worden. Het kan weliswaar door het commando rostopic echo $_topic_name te gebruiken alleen is dit commando eigenlijk bedoeld om makkelijk te debuggen.

Als ik jou was dan zou ik de volgende tutorial even doorlezen: http://wiki.ros.org/ROS/T...herSubscriber%28python%29. En dan in het bijzonder de subscriber node die er als volgt uit ziet:


code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import rospy
from std_msgs.msg import String

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
    
def listener():

    rospy.init_node('listener', anonymous=True)
    rospy.Subscriber("chatter", String, callback) 
    rospy.spin()

if __name__ == '__main__':
    listener()


In de callback functie krijg je in principe terug wat jij wilt hebben.
Elke keer dat er een message op het "chatter" topic wordt gepubliceerd wordt de callback functie aangeroepen.
Ik ben zelf bezig met ROS en had inderdaad de door jou gelinkte pagina ook gevonden.
Waar ik echter mee zit, is dat mijn Python script een "los" script is, en geen ROS package is. Dus ik start het script éénmaal in de terminal (python3 script.py), waarna het eindeloos in de terminal draait. Voor zover ik het begrijp, werkt jouw code alleen als je van dat script een ROS package maakt (toch?). Uiteindelijk moet deze code in een Python automatiseringsscript komen, dus vandaar dat het oneindig draait.

Edit, ter uitbreiding: ik hoef niet continu het topic te beluisteren, slechts als ik de robot onderweg stuur. Dan wil ik weten als hij is aangekomen, zodat ik dan de robotarm kan aansturen (dit gebeurt ook vanuit mijn Python script).

@ValHallASW Als ik het script in een terminal open, krijg ik geen meldingen o.i.d. te zien, maar in de IDE krijg ik dezelfde foutmeldingen die jij hebt.

[Voor 17% gewijzigd door Anoniem: 1277286 op 03-06-2020 18:53]


Acties:
  • +1Henk 'm!

  • CCJJVV
  • Registratie: maart 2016
  • Laatst online: 03:29
The3Designer schreef op woensdag 3 juni 2020 @ 18:51:
[...]

Ik ben zelf bezig met ROS en had inderdaad de door jou gelinkte pagina ook gevonden.
Waar ik echter mee zit, is dat mijn Python script een "los" script is, en geen ROS package is. Dus ik start het script éénmaal in de terminal (python3 script.py), waarna het eindeloos in de terminal draait. Voor zover ik het begrijp, werkt jouw code alleen als je van dat script een ROS package maakt (toch?). Uiteindelijk moet deze code in een Python automatiseringsscript komen, dus vandaar dat het oneindig draait.

@ValHallASW Als ik het script in een terminal open, krijg ik geen meldingen o.i.d. te zien, maar in de IDE krijg ik dezelfde foutmeldingen die jij hebt.
Ik moet eerlijk bekennen dat ik zelf nog nooit ROS i.c.m. python gebruikt heb en alleen ervaring met ROS i.c.m. C++ heb. In C++ kan je in ieder geval een "ROS-node" maken zonder dat deze in een package hoeft te zitten.

Als ik het volgende antwoord mag geloven is dit voor python ook mogelijk en zou het niet uitmaken of deze in een package zit of niet.


Als je maar naar één bericht wilt luisteren kan je in de callback simpelweg unregister() aanroepen. Het zou er dan ongeveer zo uit zien:

code:
1
2
3
4
5
def callback(msg):
   subscriber.unregister()

global subscriber
subscriber = rospy.Subscriber('my_topic,', String, callback)



Edit:

Daarnaast zie ik ook nog niet waarom je jouw zogenoemde automatiseringsscript niet als ROS-package wilt hebben. Je maakt immers gebruik van ROS dus waarom niet?

[Voor 15% gewijzigd door CCJJVV op 03-06-2020 19:11]


Anoniem: 1277286

Topicstarter
CCJJVV schreef op woensdag 3 juni 2020 @ 18:55:
[...]


Ik moet eerlijk bekennen dat ik zelf nog nooit ROS i.c.m. python gebruikt heb en alleen ervaring met ROS i.c.m. C++ heb. In C++ kan je in ieder geval een "ROS-node" maken zonder dat deze in een package hoeft te zitten.

Als ik het volgende antwoord mag geloven is dit voor python ook mogelijk en zou het niet uitmaken of deze in een package zit of niet.


Als je maar naar één bericht wilt luisteren kan je in de callback simpelweg unregister() aanroepen. Het zou er dan ongeveer zo uit zien:

code:
1
2
3
4
5
def callback(msg):
   subscriber.unregister()

global subscriber
subscriber = rospy.Subscriber('my_topic,', String, callback)
Hmmm, het moet inderdaad wel kunnen om een los Python script als node te runnen. Het probleem is dat ik niet weet wanneer het bericht komt, en er verder meer informatie op het desbetreffende topic wordt geplaatst. Maar misschien dat ik dan beter op het ROS forum kan vragen naar een geschikte oplossing...

Acties:
  • +1Henk 'm!

  • CCJJVV
  • Registratie: maart 2016
  • Laatst online: 03:29
The3Designer schreef op woensdag 3 juni 2020 @ 19:13:
[...]

Hmmm, het moet inderdaad wel kunnen om een los Python script als node te runnen. Het probleem is dat ik niet weet wanneer het bericht komt, en er verder meer informatie op het desbetreffende topic wordt geplaatst. Maar misschien dat ik dan beter op het ROS forum kan vragen naar een geschikte oplossing...
Dat je niet weet wanneer het bericht binnenkomt is inprincipe niet erg? Nadat je de subscriber hebt aangemaakt en in je hoofd-loop rospy.rosSpinOnce() blijft aanroepen zal voor elk bericht dat op het topic gepubliceerd wordt de callback-functie aangeroepen worden.

Daarnaast is het dan misschien handig om verschillende topics aan te maken waarbij elk topic zijn eigen taak heeft.


Aangezien je aangeeft dat je de robot aanstuurt neem ik aan dat die robot gebruik maakt van ROS omdat deze uiteindelijk ook het door jou gewilde bericht verstuurd.

Ik heb geen idee hoe gevorderd je bent in het gebruik van ROS en programmeren in het algemeen. Maar je zou naar ROS-services en ROS-actions kunnen kijken, die zijn volgens mij voor jouw doeleinde perfect.
Maar nogmaals, je gaat qua moeilijkheidsgraad dan alweer een trapje omhoog.


Edit:

In je post hieronder zie ik dat je al gebruik maakt van een ROS-action 8)7 d:)b

[Voor 3% gewijzigd door CCJJVV op 03-06-2020 19:31]


Acties:
  • +1Henk 'm!

Anoniem: 1277286

Topicstarter
Oké, ik had toch wat verder door moeten prutsen met @CCJJVV zijn post:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import rospy
from std_msgs.msg import String

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
    
def listener():

    rospy.init_node('listener', anonymous=True)
    rospy.Subscriber("chatter", String, callback) 
    rospy.spin()

if __name__ == '__main__':
    listener()
Ik heb dit aangepast naar:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import rospy
from move_base_msgs.msg import MoveBaseActionResult

def callback(data):
    if data.status.text.find("Goal reached") != -1
      print("Navigation goal has been reached!")
    
def listener():

    rospy.init_node('listener', anonymous=True)
    rospy.Subscriber("/move_base/result", MoveBaseActionResult, callback) 
    rospy.spin()

if __name__ == '__main__':
    listener()

Dit werkt perfect d:)b

Dus bedankt @ValHallASW voor het meedenken w.b.h. Python gedeelte, en @CCJJVV bedankt voor het aanraden van de Python subscriber code. Had hier toch zelf wat langer mee moeten prutsen. Maar goed, het werkt :)
Pagina: 1


Apple iPad Pro (2021) 11" Wi-Fi, 8GB ram Microsoft Xbox Series X LG CX Google Pixel 5a 5G Sony XH90 / XH92 Samsung Galaxy S21 5G Sony PlayStation 5 Nintendo Switch Lite

Tweakers vormt samen met Hardware Info, AutoTrack, Gaspedaal.nl, Nationale Vacaturebank, Intermediair en Independer DPG Online Services B.V.
Alle rechten voorbehouden © 1998 - 2021 Hosting door True