Merge remote-tracking branch 'origin/tellupdater'
Build and Publish / build (push) Successful in 23s

# Conflicts:
#	common/pyproject.toml
This commit is contained in:
2025-06-17 14:22:04 +02:00
6 changed files with 158 additions and 46 deletions
+9 -1
View File
@@ -7,12 +7,20 @@ requires-python = ">=3.11"
dependencies = [
"pydantic==2.11.4",
"numpy==2.2.5",
"jfjoch_client==1.0.0rc45"
"jfjoch_client==1.0.0rc45",
"aareDB==0.1.1a6"
]
[lint]
ignore = ["F401", "F541", "W503", "W504"]
[tool.uv.sources]
aaredb = { index = "psi"}
[[tool.uv.index]]
name = "psi"
url = "https://gitea.psi.ch/api/packages/mx/pypi/simple"
[build-system]
requires = ["setuptools>=75.6.0"]
build-backend = "setuptools.build_meta"
+10 -2
View File
@@ -13,14 +13,22 @@ dependencies = [
"fastapi==0.115.12",
"uvicorn==0.34.2",
"ultralytics==8.3.133",
"aareDB==0.1.1a4",
"aareDB==0.1.1a6",
"opencv-python-headless==4.11.0.86",
"python_multipart==0.0.20"
"python_multipart==0.0.20",
"websocket-client"
]
[lint]
ignore = ["F401", "F541", "W503", "W504"]
[tool.uv.sources]
aaredb = { index = "psi"}
[[tool.uv.index]]
name = "psi"
url = "https://gitea.psi.ch/api/packages/mx/pypi/simple"
[build-system]
requires = ["setuptools>=75.6.0"]
build-backend = "setuptools.build_meta"
+13 -30
View File
@@ -1,15 +1,18 @@
import io
import os
from typing import List, Optional
import aareDBclient
import cv2
import numpy as np
import requests
from aareDBclient import SetTellPosition, SampleEventCreate, SetTellPositionRequest
from aareDBclient import (
SetTellPosition,
SampleEventCreate,
SetTellPositionRequest )
from aaredaqlib.models import (
SampleShortInfo,
PuckLoadedInfo,
PuckInfo,
DewarAddress,
SampleShortInfoList,
)
@@ -21,38 +24,18 @@ class AareWrapper:
def __init__(
self,
bl: MXBeamline,
host: str = "https://mx-aare-test.psi.ch:1492",
host: str = "https://mx-db-01.psi.ch/dispatcher",
):
configuration = aareDBclient.Configuration(host=host)
configuration.verify_ssl = False # Disable SSL verification
self.client = aareDBclient.ApiClient(configuration)
self.client.default_headers["X-Shared-Password"] = os.getenv("AAREDB_SHARED_PASSWORD")
self.__host = host
self.__puck_api = aareDBclient.PucksApi(self.client)
self.__sample_api = aareDBclient.SamplesApi(self.client)
self.__tell_api = aareDBclient.TellsRunnerApi(self.client)
self.__sample_api = aareDBclient.SamplesRunnerApi(self.client)
self.__bl = bl
def get_pucks_beamline(self) -> List[PuckInfo]:
puck_list = self.__puck_api.get_pucks_by_slot_pucks_slot_slot_identifier_get(
slot_identifier=self.__bl.value.upper()
)
ret = []
for i in puck_list:
if i.tell_position is not None and len(i.tell_position) == 2:
d = DewarAddress(segment=i.tell_position[0], pos=i.tell_position[1])
else:
d = None
ret.append(
PuckInfo(
db_id=i.id,
puck_name=i.puck_name,
dewar_name=i.dewar_name,
user=i.pgroup,
location=d,
)
)
return ret
def set_pucks_beamline(self, input_list: List[PuckLoadedInfo]):
o = []
@@ -64,14 +47,14 @@ class AareWrapper:
)
o.append(t)
payload = SetTellPositionRequest(pucks = o, tell=self.__bl.value.upper())
ret = self.__puck_api.set_tell_positions_pucks_set_tell_positions_put(
ret = self.__tell_api.set_tell_positions(
set_tell_position_request=payload,
)
print(ret)
def get_sample_info(self) -> SampleShortInfoList:
sample_list = (
self.__puck_api.get_pucks_with_tell_position_pucks_with_tell_position_get(tell=self.__bl.value.upper())
self.__tell_api.get_pucks_with_tell_position(tell=self.__bl.value.upper())
)
ret = []
@@ -102,7 +85,7 @@ class AareWrapper:
def sample_mounted(self, s: Optional[SampleShortInfo]):
if s is not None:
try:
self.__sample_api.create_sample_event_samples_samples_sample_id_events_post(
self.__sample_api.create_sample_event(
sample_id=s.db_id,
sample_event_create=SampleEventCreate(event_type="Mounted"),
)
@@ -112,7 +95,7 @@ class AareWrapper:
def sample_unmounted(self, s: Optional[SampleShortInfo]):
if s is not None:
try:
self.__sample_api.create_sample_event_samples_samples_sample_id_events_post(
self.__sample_api.create_sample_event(
sample_id=s.db_id,
sample_event_create=SampleEventCreate(event_type="Unmounted"),
)
+116
View File
@@ -0,0 +1,116 @@
import os
import json
import websocket
import threading
import time
from aareDBclient.models import PuckWithTellPosition
from mxlibs3.tell_client import TellClient
from aaredb import AareWrapper # Make sure the import path fits your project
from aaredaqlib.beamline import MXBeamline
# Configuration
SLOT_IDENTIFIER = "X06DA"
WS_URL = f"wss://mx-db-01.psi.ch/dispatcher/protected_router/wstell/ws/slot/{SLOT_IDENTIFIER}"
WS_HEADERS = [f"X-Shared-Password: {os.getenv('AAREDB_SHARED_PASSWORD')}"]
POLL_INTERVAL = 30 # seconds
# Initialize TELL client and DB wrapper
beamline = MXBeamline.X06DA # Use your beamline enum/value
tell_client = TellClient(bl=beamline)
aare_db = AareWrapper(bl=beamline)
# Track current state
current_pucks = []
last_pucks_sent = []
def compare_and_report_change(old, new, key_func):
old_ids = set(key_func(p) for p in old)
new_ids = set(key_func(p) for p in new)
joined = new_ids - old_ids
left = old_ids - new_ids
return joined, left
def ws_update_samples_info(pucks):
"""Send sample info to TELL robot."""
tell_client.set_samples_info(pucks)
def convert_pucks(puck_list):
return [PuckWithTellPosition(
id=p.puck_id,
puck_name=p.puck_name,
puck_type=getattr(p, 'puck_type', None),
puck_location_in_dewar=getattr(p, 'puck_location_in_dewar', None),
dewar_id=getattr(p, 'dewar_id', None),
dewar_name=getattr(p, 'dewar_name', None),
pgroup=getattr(p, 'pgroup', None),
samples=getattr(p, 'samples', None),
tell_position=getattr(p, 'tell_position', None),
) for p in puck_list]
def pucks_equal(pucks1, pucks2):
# Implement a simple equality check, e.g., comparing sorted ids, or full data
if len(pucks1) != len(pucks2): return False
return all(p1.id == p2.id for p1, p2 in zip(sorted(pucks1, key=lambda p: p.id),
sorted(pucks2, key=lambda p: p.id)))
def timer_update_pucks_beamline():
global last_pucks_sent
try:
detected_pucks = tell_client.get_detected_pucks()
aare_db.set_pucks_beamline(detected_pucks)
except Exception as exc:
print(f"[ERROR][Timer] {exc}")
def on_message(ws, message):
global current_pucks
try:
data = json.loads(message)
print("[WS][MESSAGE]", data)
if isinstance(data, list):
new_pucks = [PuckWithTellPosition(**item) for item in data]
joined, left = compare_and_report_change(
current_pucks, new_pucks, key_func=lambda p: p.id
)
if joined or left:
print(f"[WS][UPDATE] joined={joined}, left={left}")
current_pucks = new_pucks
ws_update_samples_info(new_pucks)
else:
print("[WS][NO CHANGE]")
else:
print("[WS][ERROR] Unknown message format")
except Exception as exc:
print("[WS][ERROR] Failed to parse websocket message:", exc)
def on_error(ws, error):
print("[WS][ERROR]", error)
def on_close(ws, close_status_code, close_msg):
print("[WS][CLOSE]", close_status_code, close_msg)
def on_open(ws):
print("[WS][OPEN] WebSocket opened.")
def periodic_polling():
while True:
timer_update_pucks_beamline()
time.sleep(POLL_INTERVAL)
def main():
ws = websocket.WebSocketApp(
WS_URL,
header=WS_HEADERS,
on_message=on_message,
on_error=on_error,
on_close=on_close,
on_open=on_open,
)
poll_thread = threading.Thread(target=periodic_polling, daemon=True)
poll_thread.start()
ws.run_forever(sslopt={"cert_reqs": 0})
if __name__ == "__main__":
main()
+1 -1
View File
@@ -287,7 +287,7 @@ def sa2dh(devs: BeamlineDevices, cfg: BeamlineConfig):
print(f"Error for unmounting: {e}")
devs.tell.dry(wait_cold=-1, wait=False)
# devs.tell.move_park(wait=True)
devs.tell.set_samples_info(devs.aare.get_pucks_beamline())
#devs.tell.set_samples_info(devs.aare.get_pucks_beamline())# replaced by script stream
def dh2sa(devs: BeamlineDevices, cfg: BeamlineConfig):
+9 -12
View File
@@ -12,9 +12,11 @@ from aaredaqlib.models import (
SampleShortInfo,
DewarAddress,
SampleDewarAddress,
PuckInfo,
#PuckInfo,
)
from aareDBclient import (
PuckWithTellPosition,
)
from aaredaqlib.beamline import MXBeamline # noqa: F401
from mxlibs3.pshell_client import PShellClient
@@ -191,24 +193,19 @@ class TellClient:
)
return output
def set_samples_info(self, info: List[PuckInfo]):
def set_samples_info(self, info: List[PuckWithTellPosition]):
if self.__simulation:
return
j = []
for x in info:
if x.location is None:
puck_address = ""
else:
puck_address = "{:1s}{:1d}".format(x.location.segment, x.location.pos)
j.append(
{
"userName": x.user,
"dewarName": x.dewar_name,
"userName": x.pgroup,
"dewarName": x.dewar_name or "",
"puckName": x.puck_name,
"puckType": "Unipuck",
"puckAddress": puck_address,
"puckType": "Unipuck", # could use x.puck_type
"puckAddress": x.tell_position or "",
"puckBarcode": x.puck_name,
"sampleBarcode": "",
"sampleMountCount": 0,