Skip to content

Refactor connection lifecycle for asynchronous sessions contexts#27

Open
michaelosthege wants to merge 6 commits intoCommonplaceRobotics:masterfrom
JuBiotech:contextmanager-refactor
Open

Refactor connection lifecycle for asynchronous sessions contexts#27
michaelosthege wants to merge 6 commits intoCommonplaceRobotics:masterfrom
JuBiotech:contextmanager-refactor

Conversation

@michaelosthege
Copy link
Contributor

This replaces #26 where I tried to work around an issue that I ran into due to unclean connect/disconnect/dispose resource management when connections failed.

Essentially the threads must be regarded as "single use", but this requires them to be recreated if the same CRIController should be re-used.

Even more convenient would be a context manager that deals with proper disconnects and resource disposal, regardless of user level implementation.

Based on my comment here I refactored in the following way:

  • connect is changed to raise on errors -- this is a breaking change, but it's much easier to work with as it enables passing contextual information up the stack (through exception types and messages) which is not the case with return False.
  • CRIController class is split into CRIClient and CRIController(CRIClient) which makes it possible to separate observational and actuating code based on type information.
  • CRIConnector is introduced: This is a factory-like creator of connection session contexts.
    • The context managers deal with resource disposal, which simplifies usage.
    • They are already implemented as async context managers.

I also added an example that uses the CRIConnector contexts.

This asynchronous context manager style connection is relevant for us for multiple reasons:

  • We connect (asynchronously) to OPC UA servers in parallel
  • We connect to multiple iRC controllers
  • CRIClient can be long-lived, auto-reconnectiing to monitor while CRIController is only invoked for movement
  • All this happens in a FastAPI app which needs to remain responsive

What do you think, @cpr-bar ?

@michaelosthege
Copy link
Contributor Author

While testing today I already learned that entering the context should definitely await until the first robot_state arrives. Otherwise the current position and other properties can be at their default values 🤐

@michaelosthege
Copy link
Contributor Author

I can confirm that the code from this PR works.

My two biggest concerns at this point:

  • robot_state can be outdated right after connecting. IMO it should get a timestamp and context entry should not return until the first robot_state has been received.
  • I'm not sure which steps should be taken in "take control" - in my understanding the async with connector.control() as controller context entry should put and confirm the robot into a state where a movement can start.

What do you think?

@cpr-jar cpr-jar requested a review from cpr-olt February 18, 2026 13:53
raise CRICommandError("Kinematics not ready.")
yield controller
# Graceful context exit: give up control and disable robot, then disconnect in finally block.
controller.disable()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO (MO): don't do this by default

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disable sollte nicht automatisch beim Trennen der Verbindung ausgeführt werden. Falls die Anwendung nur dazu zuständig sein sollte den Zustand des Roboters zu beobachten würde sie damit im Fehlerfall den Roboter stoppen. Falls ein automatisches Disable relevant ist, sollte das über ein Flag aktivierbar sein.

Comment on lines +1466 to +1467
if not controller.enable():
raise CRICommandError("Failed to enable robot.")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO (@cpr-olt): does this automatically do reset under the hood?

Bei unserer Anlage müssen wir immer damit rechnen, dass der Mensch eine Tür öffnet: Wäre es dafür nicht besser wenn disable() automatisch gemacht wird, damit die Motoren dann schon stromlos sind?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Kapitel 4.6. sind diese Kommandos nicht genauer beschrieben:

CRISTART 1234 CMD Connect CRIEND
CRISTART 1234 CMD Disconnect CRIEND
CRISTART 1234 CMD Reset CRIEND
CRISTART 1234 CMD Enable CRIEND
CRISTART 1234 CMD Disable CRIEND

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Die Anweisungen Connect und Disconnect sind seit Version 13 nicht mehr implementiert, da sie für das aktuelle Konzept nicht relevant sind.
  • Reset setzt die Hardwarefehler zurück und übernimmt die Ist-Positionen von den Achsmodulen
  • Enable führt Reset aus und aktiviert dann die Hardware (wenn kein Fehler vorliegt). Also von Zustand MNE ("Motors not Enabled") zu Zustand "No Error".
  • Disable deaktiviert die Hardware (MNE)
    Wir werden den Abschnitt in der CRI-Doku erweitern.

Zu Ihrer Anlage: Ich denke hier ist eine hardwareseitige Lösung sicherer. Üblicherweise empfehlen wir den Türschalter so einzubinden, dass beim Öffnen der Tür der Notauskreis geöffnet wird. Je nach Schaltschrankvariante / -verkabelung wird dies dann als Notaus, Unterspannung, oder beim Versuch zu Bewegen als Encoderfehler erkannt und der Roboter stoppt den Prozess selbstständig.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Danke für die Erläuterung, @cpr-mab!

Ich werde die Implementierung entsprechend anpassen.

Außerdem habe ich gemerkt, dass häufiges connect/disconnect in der Praxis nicht stabil funktioniert.
Ich werde den CRIConnector nochmal kritisch hinterfragen und abändern oder sogar wieder raus nehmen.

Üblicherweise empfehlen wir den Türschalter so einzubinden, dass beim Öffnen der Tür der Notauskreis geöffnet wird.

Ja genau so eine Sicherheitsschaltung haben wir. Ich bezog mich auf die Situation, dass der Roboter eh still steht und der Stromverlust ggf. weniger Fehler erzeugt wenn bereits DISABLE gesendet wurde.

@cpr-mab
Copy link

cpr-mab commented Mar 11, 2026

The maintainer of this repository currently is not available. I'm no expert on this library or Python but as far as I can tell this PR looks good except for the controller.disable() in cri_controller.py, line 1479.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants