From 8a7a4d7fc1d81a219dad5a3cead0030463e61a41 Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:19:07 +0100 Subject: [PATCH 01/13] DAQ: tellupdater script and spreadsheetupdater updated for X10SA --- daq/src/aaredaq/spreadsheetupdater.py | 98 ++++++++++++++++----------- daq/src/aaredaq/tellupdater.py | 93 ++++++++++++++++++------- 2 files changed, 128 insertions(+), 63 deletions(-) diff --git a/daq/src/aaredaq/spreadsheetupdater.py b/daq/src/aaredaq/spreadsheetupdater.py index 1303c2d9..110e0002 100644 --- a/daq/src/aaredaq/spreadsheetupdater.py +++ b/daq/src/aaredaq/spreadsheetupdater.py @@ -2,13 +2,12 @@ import os import json import websocket import time -import signal -from aareDBclient.models import PuckWithTellPosition, DataCollectionParameters +from aareDBclient.models import PuckWithTellPosition from aaredaqlib.models import SampleShortInfoList, SampleShortInfo, DewarAddress from config import BeamlineConfig from aaredaqlib.beamline import MXBeamline -SLOT_IDENTIFIER = "X06DA" +SLOT_IDENTIFIER = "X10SA" WS_URL = f"wss://mx-db-01.psi.ch/dispatcher/protected_router/tell_runner/ws/samples-spreadsheet/{SLOT_IDENTIFIER}" # Ensure the environment variable for the shared password is set @@ -17,7 +16,7 @@ if not password: raise ValueError("The AAREDB_SHARED_PASSWORD environment variable is not set.") WS_HEADERS = [f"X-Shared-Password: {password}"] -beamline = MXBeamline.X06DA +beamline = MXBeamline.X10SA config = BeamlineConfig(bl=beamline) # Cache the current spreadsheet for change detection @@ -40,55 +39,74 @@ def on_message(ws, message): try: data = json.loads(message) - # Process the pucks data from the "samples" key in the message - if isinstance(data, dict) and "samples" in data: - pucks_data = data["samples"] - else: - pucks_data = data # fallback: might be a list directly - - # Convert dictionary data into PuckWithTellPosition Pydantic model instances + pucks_data = data["samples"] if isinstance(data, dict) and "samples" in data else data pucks = [PuckWithTellPosition(**item) for item in pucks_data] - sample_short_infos = [] + print(f"INFO: Received pucks: {[p.puck_name for p in pucks]}") + + normal_short_infos = [] + reference_short_infos = [] for p in pucks: - # Iterate through samples in the puck; `s` is a Pydantic `Sample` object for s in p.samples or []: - # Handle dewar_address dewar_address = None - if p.tell_position is not None and len(p.tell_position) == 2: - dewar_address = DewarAddress( - segment=p.tell_position[0], pos=p.tell_position[1] - ) + target_list = None - aaredb_params = s.data_collection_parameters + # Reference tool + if isinstance(p.tell_position, str) and p.tell_position.startswith("X"): + dewar_address = DewarAddress(segment="X", pos=int(p.tell_position[1:]) if len(p.tell_position) > 1 else 1) + target_list = reference_short_infos - # Build SampleShortInfo using attribute access. - sample_short_infos.append( - SampleShortInfo( - db_id=s.id, - puck_name=p.puck_name, - dewar_name="Unknown" if p.dewar_name is None else p.dewar_name, - sample_name=s.sample_name, - user=s.pgroup, - pin=s.position, # NOTE: This might need conversion if `pin` is an int and `position` is a string. - location=dewar_address, - priority=s.priority if hasattr(s, "priority") and s.priority is not None else 1.0, - comment=s.comments if hasattr(s, "comments") else None, - mount_count=s.mount_count or 0, - aaredb_params=aaredb_params - ) + # Normal puck like "A1", "B5", "C3" + elif isinstance(p.tell_position, str): + segment = p.tell_position[0] + pos = int(p.tell_position[1:]) + dewar_address = DewarAddress(segment=segment, pos=pos) + target_list = normal_short_infos + + else: + print(f"[WARN] Skipping unknown tell_position format: {p.tell_position}") + continue + + info = SampleShortInfo( + db_id=s.id, + puck_name=p.puck_name, + dewar_name=p.dewar_name or "Unknown", + sample_name=s.sample_name, + run_number=s.run_number, + user=s.pgroup, + pin=s.position, + location=dewar_address, + priority=s.priority,#getattr(s, "priority", 1.0), + comment=getattr(s, "comments", None), + mount_count=s.mount_count or 0, + aaredb_params=s.data_collection_parameters ) - # Update Redis with the new spreadsheet - ret = SampleShortInfoList(s=sample_short_infos) - config.spreadsheet = ret - print("DEBUG: Writing to redis key:", config._BeamlineConfig__bl + ":sample_spreadsheet") - print("[REDIS][INFO] Written spreadsheet to redis via .spreadsheet property") + target_list.append(info) + + # Write normal pucks to sample_spreadsheet + normal_key = f"{config._BeamlineConfig__bl}:sample_spreadsheet" + normal_list = SampleShortInfoList(s=normal_short_infos+reference_short_infos) + config._BeamlineConfig__client.set(normal_key, normal_list.json()) + print("[REDIS][INFO] Written normal spreadsheet to:", normal_key) + + # Write reference tools to reference-tools + ref_key = f"{config._BeamlineConfig__bl}:reference-tools" + if reference_short_infos: + ref_list = SampleShortInfoList(s=reference_short_infos) + config._BeamlineConfig__client.set(ref_key, ref_list.json()) + print("[REDIS][INFO] Written reference tools to:", ref_key) + else: + # Clear key if empty + try: + config._BeamlineConfig__client.delete(ref_key) + print("[REDIS][INFO] Cleared reference tools key:", ref_key) + except Exception: + pass except Exception as exc: print("[WS][ERROR] Failed to parse or convert message:", exc) - def on_error(ws, error): """ Handle WebSocket errors. diff --git a/daq/src/aaredaq/tellupdater.py b/daq/src/aaredaq/tellupdater.py index d901320d..b38b5231 100644 --- a/daq/src/aaredaq/tellupdater.py +++ b/daq/src/aaredaq/tellupdater.py @@ -3,29 +3,30 @@ import json import websocket import sseclient import requests -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 +#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}" +SLOT_IDENTIFIER = "X10SA" +#WS_URL = f"wss://mx-db-01.psi.ch/dispatcher/protected_router/wstell/ws/slot/{SLOT_IDENTIFIER}" +WS_URL = f"wss://localhost:8001/protected_router/wstell/ws/slot/{SLOT_IDENTIFIER}" WS_HEADERS = [f"X-Shared-Password: {os.getenv('AAREDB_SHARED_PASSWORD')}"] +print(WS_HEADERS) # Initialize TELL client and DB wrapper -beamline = MXBeamline.X06DA # Use your beamline enum/value -tell_client = TellClient(bl=beamline) -aare_db = AareWrapper(bl=beamline) +beamline = None #MXBeamline.X06DA # Use your beamline enum/value +tell_client = None #TellClient(bl=beamline) +aare_db = None #AareWrapper(bl=beamline) # Track current state current_pucks = [] last_pucks_sent = [] def listen_to_sse(): - print("[DEBUG] Entered listen_to_sse()") + print("[SSE][listen_to_sse] Entered listen_to_sse()") if not tell_client.url: print(f"[SSE][WARN] No TELL URL configured – SSE listener not started. (tell_client.url={tell_client.url})") return @@ -33,23 +34,43 @@ def listen_to_sse(): try: response = requests.get(sse_url, stream=True) client = sseclient.SSEClient(response) + + # Immediately fetch current detected pucks once on connect so the system starts with + # up-to-date state (instead of waiting for the first DewarContentUpdate event). + try: + print("[SSE][listen_to_sse] Initial detected pucks fetch on connect") + handle_tell_change_event() + except Exception as exc: + print(f"[SSE][listen_to_sse][WARN] Initial fetch failed: {exc}") + for event in client.events(): if event.event == "DewarContentUpdate": - print(f"event: {event.event}, data: {event.data}") + print(f"[SSE][listen_to_sse] event: {event.event}, data: {event.data}") on_sse_event(event) except requests.exceptions.RequestException as exc: - print(f"[SSE][ERROR] Failed to connect to {sse_url}: {exc}") + print(f"[SSE][listen_to_sse][ERROR] Failed to connect to {sse_url}: {exc}") 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 + """ + Compare two lists of pucks and return (joined, left). + Ignore reference-tools puck(s) with tell_position 'X1'. + """ + # Filter out reference-tools + old_filtered = [p for p in old if getattr(p, "tell_position", None) != "X1"] + new_filtered = [p for p in new if getattr(p, "tell_position", None) != "X1"] + + old_ids = set(key_func(p) for p in old_filtered) + new_ids = set(key_func(p) for p in new_filtered) + + joined = new_ids - old_ids # newly added + left = old_ids - new_ids # removed + return joined, left def ws_update_samples_info(pucks): """Send sample info to TELL robot.""" - tell_client.set_samples_info(pucks) + #tell_client.set_samples_info(pucks) + print(pucks) def handle_tell_change_event(): """Fetch the latest detected pucks from TELL and update the database.""" @@ -68,16 +89,42 @@ def on_sse_event(event): handle_tell_change_event() def on_message(ws, message): + #print("[WS][MESSAGE]", message) try: data = json.loads(message) print("[WS][MESSAGE]", data) - if isinstance(data, list): - new_pucks = [PuckWithTellPosition(**item) for item in data] - # Always forward the current state to TELL or your API + if not isinstance(data, list): + print("[WS][ERROR] Unknown message format") + return + + new_pucks = [PuckWithTellPosition(**item) for item in data] + + # Keep module-level current_pucks in sync + global current_pucks + + # First, try to refresh DB/TELL state to avoid overwriting a recent SSE update. + try: + # This will fetch detected pucks from TELL and update DB via your existing handler + handle_tell_change_event() + # Small wait gives SSE/DB handler a moment to settle (optional) + time.sleep(0.2) + except Exception as exc: + + print(f"[WS][WARN] handle_tell_change_event() failed: {exc}") + + # Compare lists and ignore reference puck at X1 if needed + joined, left = compare_and_report_change(current_pucks, new_pucks, lambda p: getattr(p, "puck_name", None)) + + # Update local cache + current_pucks = new_pucks + + if joined or left: + print(f"[WS][INFO] Changes detected joined={joined} left={left}") + # Send only when there is a real change ws_update_samples_info(new_pucks) print("[WS][INFO] Sent current pucks state to TELL.") else: - print("[WS][ERROR] Unknown message format") + print("[WS][INFO] No change, skipping send to TELL.") except Exception as exc: print("[WS][ERROR] Failed to parse websocket message:", exc) @@ -92,8 +139,8 @@ def on_open(ws): def main(): # Start SSE listener in a separate background thread - sse_thread = threading.Thread(target=listen_to_sse, daemon=True) - sse_thread.start() + #sse_thread = threading.Thread(target=listen_to_sse, daemon=True) + #sse_thread.start() # Main thread runs websocket client loop while True: From 58aeaddd95f0ac0fb63498cd16252018e4b21fee Mon Sep 17 00:00:00 2001 From: appleb_m Date: Wed, 18 Feb 2026 13:33:42 +0100 Subject: [PATCH 02/13] Update common/pyproject.toml --- common/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/pyproject.toml b/common/pyproject.toml index b43eeb87..8e7c7feb 100644 --- a/common/pyproject.toml +++ b/common/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.11" dependencies = [ "pydantic==2.11.4", "numpy==2.2.5", - "jfjoch_client==1.0.0rc113", + "jfjoch_client==1.0.0rc124", ] [lint] From 326e7650cb87d06314957e0e5f9ee93ed2ae8349 Mon Sep 17 00:00:00 2001 From: appleb_m Date: Wed, 18 Feb 2026 13:34:35 +0100 Subject: [PATCH 03/13] Update gui/pyproject.toml --- gui/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/pyproject.toml b/gui/pyproject.toml index 86896082..2e20f6f4 100644 --- a/gui/pyproject.toml +++ b/gui/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaregui" -version = "0.2.69" +version = "0.2.70" description = "Beamline control GUI" readme = "README.md" requires-python = ">=3.11" @@ -9,7 +9,7 @@ dependencies = [ "pyzmq==26.4.0", "opencv-python-headless==4.11.0.86", "PySide6==6.9.0", - "aaredaqlib==0.2.69" + "aaredaqlib==0.2.70" ] [lint] From c5ae6e4e94e8db005d6366d312ad86489e4bcdc5 Mon Sep 17 00:00:00 2001 From: appleb_m Date: Wed, 18 Feb 2026 13:35:33 +0100 Subject: [PATCH 04/13] Update daq/pyproject.toml --- daq/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daq/pyproject.toml b/daq/pyproject.toml index 8b75faa0..70648f77 100644 --- a/daq/pyproject.toml +++ b/daq/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaredaq" -version = "0.2.69" +version = "0.2.70" description = "AareDAQ data acquisition server" readme = "README.md" requires-python = ">=3.11" @@ -18,7 +18,7 @@ dependencies = [ "python_multipart==0.0.20", "websocket-client==1.8.0", "sseclient-py==1.8.0", - "aaredaqlib==0.2.69" + "aaredaqlib==0.2.70" ] [lint] From 996cfca745ae0d3c2170e2e3ae7e097a69222fed Mon Sep 17 00:00:00 2001 From: appleb_m Date: Wed, 18 Feb 2026 13:36:36 +0100 Subject: [PATCH 05/13] Update common/pyproject.toml --- common/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/pyproject.toml b/common/pyproject.toml index 8e7c7feb..0b0b2dfe 100644 --- a/common/pyproject.toml +++ b/common/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaredaqlib" -version = "0.2.69" +version = "0.2.70" description = "Libraries shared between AareDAQ and AareGUI" readme = "README.md" requires-python = ">=3.11" From bb0ad68db75e9ce9387b3f985be9bcada93887e6 Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:30:27 +0100 Subject: [PATCH 06/13] Update versions and modify `aperture` field in `DataCollectionParameters` Incremented project versions for `aaredaq`, `aaregui`, and `aaredaqlib` to `0.2.71`. Updated dependencies to ensure consistency across modules. Changed the `aperture` field in `DataCollectionParameters` from `Optional[str]` to `Optional[int]` in `aaredaqlib`. --- common/pyproject.toml | 2 +- common/src/aaredaqlib/models.py | 2 +- daq/pyproject.toml | 4 ++-- gui/pyproject.toml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/pyproject.toml b/common/pyproject.toml index 0b0b2dfe..04d3a25d 100644 --- a/common/pyproject.toml +++ b/common/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaredaqlib" -version = "0.2.70" +version = "0.2.71" description = "Libraries shared between AareDAQ and AareGUI" readme = "README.md" requires-python = ">=3.11" diff --git a/common/src/aaredaqlib/models.py b/common/src/aaredaqlib/models.py index aa10ea66..f11b6ac6 100644 --- a/common/src/aaredaqlib/models.py +++ b/common/src/aaredaqlib/models.py @@ -59,7 +59,7 @@ class DataCollectionParameters(BaseModel): int ] = None # Only accept positive integers between 0 and 100 targetresolution: Optional[float] = None # Only accept positive float - aperture: Optional[str] = None # Optional string field + aperture: Optional[int] = None # Optional string field datacollectiontype: Optional[ str ] = None # Only accept "standard", other types might be added later diff --git a/daq/pyproject.toml b/daq/pyproject.toml index 70648f77..18786c39 100644 --- a/daq/pyproject.toml +++ b/daq/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaredaq" -version = "0.2.70" +version = "0.2.71" description = "AareDAQ data acquisition server" readme = "README.md" requires-python = ">=3.11" @@ -13,7 +13,7 @@ dependencies = [ "fastapi==0.115.13", "uvicorn==0.34.2", "ultralytics==8.3.133", - "aaredb==0.1.1a33", + "aaredb==0.1.1a42", "opencv-python-headless==4.11.0.86", "python_multipart==0.0.20", "websocket-client==1.8.0", diff --git a/gui/pyproject.toml b/gui/pyproject.toml index 2e20f6f4..deb0a82f 100644 --- a/gui/pyproject.toml +++ b/gui/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaregui" -version = "0.2.70" +version = "0.2.71" description = "Beamline control GUI" readme = "README.md" requires-python = ">=3.11" @@ -9,7 +9,7 @@ dependencies = [ "pyzmq==26.4.0", "opencv-python-headless==4.11.0.86", "PySide6==6.9.0", - "aaredaqlib==0.2.70" + "aaredaqlib==0.2.71" ] [lint] From b87dd20fc3ebc862e274c5efdd664f23f1d692a9 Mon Sep 17 00:00:00 2001 From: appleb_m Date: Wed, 18 Feb 2026 16:35:11 +0100 Subject: [PATCH 07/13] Update Version build and publish --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b816fa80..c386bcde 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.69 +0.2.71 From a083bb6cf1b5753d0a235820bc3c4ab92b4da563 Mon Sep 17 00:00:00 2001 From: appleb_m Date: Wed, 18 Feb 2026 16:37:40 +0100 Subject: [PATCH 08/13] Fix bug in workflows build and publish --- .gitea/workflows/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/action.yaml b/.gitea/workflows/action.yaml index 0873f206..79686358 100644 --- a/.gitea/workflows/action.yaml +++ b/.gitea/workflows/action.yaml @@ -55,7 +55,7 @@ jobs: mv dist/* ../dist - name: Upload Package to Gitea PyPI - if: github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'build and publish') + if: github.ref == 'refs/heads/master' && contains(github.event.head_commit.message, 'build and publish') env: TWINE_USERNAME: "__token__" # Username for Twine when using token-based auth TWINE_PASSWORD: ${{ secrets.PIP_REPOSITORY_API_TOKEN }} # Use the secret for authentication From 423cd684684244977b53155a0057fd056d9cb1a2 Mon Sep 17 00:00:00 2001 From: appleb_m Date: Wed, 18 Feb 2026 16:39:29 +0100 Subject: [PATCH 09/13] Update daq/pyproject.toml --- daq/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daq/pyproject.toml b/daq/pyproject.toml index 18786c39..fda8b86b 100644 --- a/daq/pyproject.toml +++ b/daq/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "python_multipart==0.0.20", "websocket-client==1.8.0", "sseclient-py==1.8.0", - "aaredaqlib==0.2.70" + "aaredaqlib==0.2.71" ] [lint] From eb2d6b91cfd119e7c6f905b741b27f958df5b2db Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:47:12 +0100 Subject: [PATCH 10/13] jfjoch_client version update to 1.0.0rc125, build and release --- common/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/pyproject.toml b/common/pyproject.toml index 04d3a25d..090df1b7 100644 --- a/common/pyproject.toml +++ b/common/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.11" dependencies = [ "pydantic==2.11.4", "numpy==2.2.5", - "jfjoch_client==1.0.0rc124", + "jfjoch_client==1.0.0rc125", ] [lint] From 5e36c42cde867cbb96c34ef752b525afc137d69d Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Wed, 18 Feb 2026 21:01:01 +0100 Subject: [PATCH 11/13] Incremented `aaredaqlib` version to `0.2.72`, build and publish --- common/pyproject.toml | 2 +- daq/pyproject.toml | 2 +- gui/pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/pyproject.toml b/common/pyproject.toml index 090df1b7..037a399e 100644 --- a/common/pyproject.toml +++ b/common/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaredaqlib" -version = "0.2.71" +version = "0.2.72" description = "Libraries shared between AareDAQ and AareGUI" readme = "README.md" requires-python = ">=3.11" diff --git a/daq/pyproject.toml b/daq/pyproject.toml index fda8b86b..2d061326 100644 --- a/daq/pyproject.toml +++ b/daq/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "python_multipart==0.0.20", "websocket-client==1.8.0", "sseclient-py==1.8.0", - "aaredaqlib==0.2.71" + "aaredaqlib==0.2.72" ] [lint] diff --git a/gui/pyproject.toml b/gui/pyproject.toml index deb0a82f..229e9eb8 100644 --- a/gui/pyproject.toml +++ b/gui/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "pyzmq==26.4.0", "opencv-python-headless==4.11.0.86", "PySide6==6.9.0", - "aaredaqlib==0.2.71" + "aaredaqlib==0.2.72" ] [lint] From 2e12df395fcafbcec609ff9af138b809dadcf2a2 Mon Sep 17 00:00:00 2001 From: GotthardG <51994228+GotthardG@users.noreply.github.com> Date: Wed, 18 Feb 2026 21:02:22 +0100 Subject: [PATCH 12/13] Incremented `aaredaqlib` version to `0.2.72`, build and publish --- daq/pyproject.toml | 2 +- gui/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daq/pyproject.toml b/daq/pyproject.toml index 2d061326..4ef913dc 100644 --- a/daq/pyproject.toml +++ b/daq/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaredaq" -version = "0.2.71" +version = "0.2.72" description = "AareDAQ data acquisition server" readme = "README.md" requires-python = ">=3.11" diff --git a/gui/pyproject.toml b/gui/pyproject.toml index 229e9eb8..edc8531e 100644 --- a/gui/pyproject.toml +++ b/gui/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "aaregui" -version = "0.2.71" +version = "0.2.72" description = "Beamline control GUI" readme = "README.md" requires-python = ">=3.11" From e962f48eb96097ec6d688c8fb40f1662d9e3db6b Mon Sep 17 00:00:00 2001 From: appleb_m Date: Tue, 10 Mar 2026 13:44:03 +0100 Subject: [PATCH 13/13] Revert changes to workflows before large merge from x10sa branch --- .gitea/workflows/action.yaml | 172 ++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/action.yaml b/.gitea/workflows/action.yaml index a06a1fc3..79686358 100644 --- a/.gitea/workflows/action.yaml +++ b/.gitea/workflows/action.yaml @@ -1,17 +1,24 @@ name: Build and Publish - on: push: tags: - '*' branches: - master + - deploy + - 'feature/**' + - gitea-pages pull_request: branches: - master + - deploy pull_request_target: types: [closed] +env: + DEPLOY_BRANCH: gitea-pages + PREVIEWS_DIR: previews + jobs: build: runs-on: ubuntu-latest @@ -28,8 +35,24 @@ jobs: - name: Build the common wheel run: | - source venv/bin/activate - python -m build + source venv/bin/activate + cd common/ + python -m build + mv dist/* ../dist + + - name: Build the GUI wheel + run: | + source venv/bin/activate + cd gui/ + python -m build + mv dist/* ../dist + + - name: Build the DAQ wheel + run: | + source venv/bin/activate + cd daq/ + python -m build + mv dist/* ../dist - name: Upload Package to Gitea PyPI if: github.ref == 'refs/heads/master' && contains(github.event.head_commit.message, 'build and publish') @@ -40,3 +63,146 @@ jobs: source venv/bin/activate twine upload --repository-url https://gitea.psi.ch/api/packages/mx/pypi \ dist/* + + docs: + name: Build and Deploy Docs + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # needed for pushing branches/tags + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Build Sphinx HTML + working-directory: daq/docs + run: | + # ensure on master + git fetch origin master:master || true + git checkout master || true + + # create venv one level up and activate it + python3 -m venv ../venv + source ../venv/bin/activate + + # ensure pip and Sphinx are installed in this venv + pip install --upgrade pip + pip install "Sphinx==8.2.3" + pip install myst-parser + pip install sphinx_immaterial + pip install linkify-it-py + + if [ -f requirements.txt ]; then + pip install -r requirements.txt + elif [ -f ../docs/requirements.txt ]; then + pip install -r ../docs/requirements.txt + else + # fallback to known packages if requirements file is not present + pip install "Sphinx==8.2.3" myst-parser sphinx_immaterial linkify-it-py + fi + + + # install project editable without deps (avoid private packages) + pip install -e ../ --no-deps + + # prefer the sphinx-apidoc CLI if available; otherwise try module fallback + if command -v sphinx-apidoc > /dev/null 2>&1; then + sphinx-apidoc -o modules/ ../src --separate --module-first --force --remove-old + else + # module fallback: some Sphinx installs expose the module at sphinx.ext.apidoc + python -m sphinx.ext.apidoc -o modules/ ../src --separate --module-first --force --remove-old + fi + + + # Build HTML (source is '.' because working-directory is docs) + # Run sphinx-build and capture exit code and full output to help debugging if it fails. + sphinx-build -b html . _build/html || { + echo "=== Sphinx build failed with exit $?: showing build output and tree ===" + echo "Contents of docs/ after build attempt:" + ls -la + echo "Contents of daq/docs/_build (if any):" + ls -la _build || true + # If sphinx produced a build log file, print it + if [ -f sphinx-build.log ]; then + echo "---- sphinx-build.log ----" + cat sphinx-build.log + fi + # Exit with non-zero to fail the job (avoid deploying empty site) + exit 1 + } + + # Sanity check: ensure HTML output exists before continuing to deploy + if [ ! -d _build/html ] || [ -z "$(ls -A _build/html)" ]; then + echo "ERROR: daq/docs/_build/html does not exist or is empty after successful sphinx-build." + ls -la _build || true + exit 1 + fi + + - name: Deploy to gitea-pages branch (Gitea) + env: + GIT_USER: "Martin Appleby (Gitea)" + GIT_EMAIL: "martin.appleby@psi.ch" + DEPLOY_TOKEN: ${{ secrets.DOCS_DEPLOY_TOKEN }} + run: | + set -e + git config --global user.name "$GIT_USER" + git config --global user.email "$GIT_EMAIL" + + # Deploy strategy: create/update branch gitea-pages and replace its contents + # with the generated daq/docs/_build/html. This keeps only the documentation + # on the gitea-pages branch. + # Confirm build output exists (relative to repository root) + BUILD_DIR="daq/docs/_build/html" + echo "Checking build output at: $BUILD_DIR" + if [ ! -d "$BUILD_DIR" ] || [ -z "$(ls -A "$BUILD_DIR")" ]; then + echo "ERROR: $BUILD_DIR does not exist or is empty" + echo "Listing docs/:" + ls -la docs || true + echo "Listing daq/docs/_build:" + ls -la daq/docs/_build || true + exit 1 + fi + # Work in a temporary clone to avoid touching the runner workspace + TMPDIR=$(mktemp -d) + REPO_URL="https://$DEPLOY_TOKEN@gitea.psi.ch/${{ github.repository }}" + git clone "$REPO_URL" "$TMPDIR" + cd "$TMPDIR" + + # Create or switch to orphan branch gitea-pages + if git rev-parse --verify --quiet gitea-pages >/dev/null; then + git checkout gitea-pages + # Make sure working tree matches branch (hard reset) + git fetch --prune origin gitea-pages || true + git reset --hard origin/gitea-pages || git reset --hard + else + git checkout --orphan gitea-pages + fi + # Remove all files tracked in the branch (leave .git) + git rm -rf . || true + # Also remove untracked files to ensure clean branch state + git clean -fdx || true + + # Copy built HTML into the clone root + echo "Copying built HTML into branch root..." + (cd "${GITHUB_WORKSPACE}/${BUILD_DIR}" && tar -cf - .) | tar -xf - -C "$TMPDIR" + + # Ensure index.html exists in destination as sanity check + if [ ! -f "$TMPDIR/index.html" ] && [ ! -f "$TMPDIR/index.htm" ]; then + # If site root index is not present, try to check subfolder + echo "Warning: no index.html found at branch root after copy. Listing files:" + ls -la "$TMPDIR" || true + fi + + git add --all + if git diff --quiet --cached; then + echo "No changes to deploy" + exit 0 + fi + COMMIT_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" + git commit -m "docs: (${COMMIT_DATE}) | auto-update documentation from ${GITHUB_SHA}" + git push --force-with-lease origin gitea-pages