Masterclass: SiLA2-Client für ICM FeliX (Python)
ICM FeliX · Laborautomatisierung · gRPC/TLS

Ein eigener SiLA2-Client — Schritt für Schritt, in Python

Vom leeren Verzeichnis zum laufenden Client, der ein komplettes Pipettier-Protokoll auf dem FeliX ausführt — inklusive Bedienhinweisen, die euer Client anzeigt und quittiert. Jeder Codeblock live gegen den Server verifiziert.

8 Module ~90 Minuten Endergebnis felix_client.py verifiziert 2026-08-06
MODUL 00

SiLA2 in fünf Minuten

SiLA 2 ist der Laborautomatisierungs-Standard für Gerätesteuerung über gRPC + TLS. Die Begriffe, die ihr braucht:

BegriffBedeutung
SiLA ServerDas Gerät bzw. seine Steuersoftware — hier das ICM FeliX (ICM.FeliX.Host)
FeatureEine benannte Fähigkeit, beschrieben als XML (FDL). Der FeliX hat zwei: das Pflicht-Feature SiLAService und com.endress/liquidhandling/LiquidHandlingController/v1
Observable CommandLang laufende Aktion: sofortige CommandExecutionUUID, Fortschritt als Stream, Ergebnis separat
Intermediate ResponseZwischenmeldung eines laufenden Commands — der FeliX nutzt sie für Bedienhinweise
Untrusted-Server-ModusTLS mit selbstsigniertem Zertifikat; der Client vertraut der Server-CA explizit
Das Entscheidende
ExecuteProtocol nimmt ein ganzes Protokoll als JSON entgegen und orchestriert den Geräte-Lifecycle selbst (INIT davor, WORKFLOW_SHUTDOWN danach). Euer Client schickt nur Body-Schritte.
MODUL 01

Setup 10 min

1 · FeliX starten (läuft er schon, weiter zu Schritt 2):

cd "/Users/marcelweissgerber/Downloads/icm-lh-development 2"
docker compose -f docker-compose.local.yml -f docker-compose.local.mac.yml up -d

Ports: 5108 REST/UI · 5109 CASE-gRPC · 5111 SiLA2 (TLS). Kontrolle: localhost:5108/sila-console — dort seht ihr später jeden Call eures Clients live.

2 · CA-Zertifikat holen — der Trust-Anker für den untrusted-Modus (der Server generiert es beim ersten Start selbst):

docker cp icm-felix-local:/opt/icm/ca.crt .

3 · Python-Umgebung — die offizielle SiLA2-Referenzimplementierung:

python3 -m venv silaenv && source silaenv/bin/activate
pip install sila2
Standard-Exkurs
Ein konformer Server verteilt die CA auch über die mDNS-TXT-Records seines _sila._tcp-Advertisements — Clients mit Discovery brauchen den Datei-Umweg nicht.
MODUL 02

Verbinden + Discovery 10 min

from sila2.client import SilaClient

ca = open("ca.crt", "rb").read()
client = SilaClient("127.0.0.1", 5111, root_certs=ca)

print(client.SiLAService.ServerName.get())    # ICM FeliX SiLA2 Server
print(client.SiLAService.ServerUUID.get())    # f91b13d1-… (stabil über Neustarts)
print(client.SiLAService.ImplementedFeatures.get())
Der Kern des SiLA-Modells
Die Bibliothek hat den FeliX nie gesehen — und kennt ihn trotzdem: sie lädt zur Laufzeit die Feature-XMLs via GetFeatureDefinition und generiert daraus den Client. client.LiquidHandlingController existiert ab jetzt einfach.

Checkpoint: Beide FQFIs erscheinen, und in der /sila-console tauchen eure Calls mit Peer-Adresse und user-agent: grpc-python… auf.

MODUL 03

Der Action-Katalog 5 min

Welche Aktionen ExecuteActivity versteht, sagt die Property ActivitySchema — identisch mit GET /api/activity/schema, eine Quelle für REST und SiLA:

import json
lhc = client.LiquidHandlingController
schema = json.loads(lhc.ActivitySchema.get())
print(schema["schemaVersion"], len(schema["actions"]))   # 2.11  38

Jede Action bringt Parameter-Namen, Datentypen und kopfspezifische Volumen-Ranges mit — euer Client validiert Protokolle, bevor das Gerät sie sieht.

MODUL 04

Observable Commands verstehen 10 min

import time
cmd = lhc.ExecuteActivity("WAIT", json.dumps([{"Key": "waitTime", "Value": "1"}]), True)
print(cmd.execution_uuid)          # sofort da — die Aktion läuft im Hintergrund
while not cmd.done: time.sleep(0.3)
result = cmd.get_responses()       # ChecklistJson mit dem Step-Ergebnis

Die Observable-Command-Trias: ① sofortige Bestätigung mit UUID · ② Info-Stream (intern, speist cmd.done) · ③ Ergebnis-Abruf, erst wenn fertig.

MODUL 05

Ganze Protokolle: ExecuteProtocol 15 min

Der eigentliche Arbeitsmodus — nur Body-Schritte, den Rest macht der Server:

protocol = {"steps": [
    {"activity": "MOVE", "parameters": [{"Key": "deckPosition", "Value": "7"},
                                    {"Key": "zReference", "Value": "Topmost"}]},
    {"activity": "WAIT", "parameters": [{"Key": "waitTime", "Value": "1"}]},
]}
cmd = lhc.ExecuteProtocol(json.dumps(protocol), True)   # True = simuliert
while not cmd.done: time.sleep(0.5)
for step in json.loads(cmd.get_responses().StepResultsJson):
    print(step["Step"], step["Activity"], step["Success"])
1 INIT True · 2 MOVE True · 3 WAIT True · 4 WORKFLOW_SHUTDOWN True

Der Server validiert vor dem ersten Gerätekommando: unbekannte Typen und Lifecycle-Actions im Body werden synchron als ValidationError abgelehnt.

MODUL 06

Bedienhinweise: Server pausiert, ihr quittiert 20 min

USER_NOTIFICATION und USER_INPUT sind normale Body-Schritte. Der Server pausiert dort (Gerät bleibt initialisiert!), meldet über den Intermediate-Stream — euer Client quittiert per ConfirmUserPrompt:

import threading

def handle_prompts():
    for intermediate in cmd.subscribe_to_intermediate_responses():
        prompt = json.loads(intermediate.PromptJson)
        print("BEDIENHINWEIS:", prompt["Message"])
        lhc.ConfirmUserPrompt(str(cmd.execution_uuid), prompt["PromptId"], "OK")

threading.Thread(target=handle_prompts, daemon=True).start()
Schritt 3 USER_NOTIFICATION: OK — Quittung 'OK' um 2026-08-06T07:36:46.335…+00:00
Evidenz — auf beiden Seiten
Euer Client besitzt den Zeitstempel (er hat den Dialog gezeigt), der Server persistiert dieselbe Quittung im Step-Ergebnis. „Bediener hat um 07:36 bestätigt, dass Spalte 1 entfernt wurde" existiert im Client-Record und im Server-Ergebnis. Unbeantwortete Hinweise → Timeout (Sila:UserPromptTimeoutSeconds, Default 300 s) → ProtocolFailed, Teardown läuft trotzdem.
MODUL 07

Fehlerbilder lesen 10 min

SiLA-Fehler sind typisiert und kommen in Python als Exceptions an:

FehlerWannWo
ValidationErrorUnbekannte Activity, Lifecycle im Body, kaputtes JSONsynchron beim Aufruf
DefinedExecutionError: ProtocolFailedStep-Fehler (999999 µL → „outside [0.1, 1000.0]"), Prompt-Timeoutbei get_responses()
DefinedExecutionError: UnknownPromptConfirmUserPrompt mit falscher/abgelaufener Idsynchron
MODUL 08

Der fertige Client 10 min

Alles zusammen — CLI, Schema-Vorab-Validierung, interaktive Quittung (oder --auto für Skripte), Ergebnis-Report: tools/masterclass/felix_client.py (~100 Zeilen, im Repo).

cd tools/masterclass
docker cp icm-felix-local:/opt/icm/ca.crt .
python felix_client.py beispiel-protokoll.json              # interaktiv
python felix_client.py beispiel-protokoll.json --auto LOT-42 # unbeaufsichtigt
Verbunden: ICM FeliX SiLA2 Server (UUID f91b13d1-6142-47e7-b806-0f49723b6fc7)
Schema v2.11: alle 4 Schritte bekannt
>>> BEDIENHINWEIS (Schritt 3): Bitte Spalte 1 aus dem RoboTipTray entfernen. → quittiert 'OK'
>>> BEDIENHINWEIS (Schritt 5): Bitte die Lot-Nummer erfassen. → quittiert 'LOT-42'
Schritt 1 INIT: OK … Schritt 6 WORKFLOW_SHUTDOWN: OK
Damit habt ihr
Discovery, Schema-Validierung, Protokoll-Ausführung mit server-seitigem Lifecycle, Bedienhinweis-Quittung mit beidseitiger Evidenz und typisiertes Fehler-Handling — die komplette Fläche, die auch briefly nutzt.
MODUL 09

Live-Lab: Protokoll bauen und WIRKLICH ausführen

Dieser Editor spricht echtes SiLA2 (gRPC-Web) mit einem FeliX auf der Testing-Umgebung — simuliert, aber derselbe Server-Code wie am Gerät. Kurs-Token eingeben, verbinden, Steps zusammenklicken, ausführen. Bedienhinweise erscheinen als Dialog und eure Quittung landet zeitgestempelt im Ergebnis.

Verbindung
nicht verbunden
ANHANG

Troubleshooting & Wie weiter

ICM FeliX · Branch feature/sila2-server · verifiziert 2026-08-06 · kanonische Quelle: documentation/masterclass-sila2-python-client.md im Repo