From 0fb841537886467028b6431d5a40c2472f1db378 Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 27 Nov 2025 11:29:20 +0100 Subject: [PATCH 1/2] Refactor project structure and update .gitignore for improved organization --- src/experiment/globals.py | 47 ++++++++++ src/experiment/run.py | 191 ++++++++++++++++++++++++++++++++++++++ src/experiment/setup.py | 144 ++++++++++++++++++++++++++++ src/experiment/utils.py | 73 +++++++++++++++ src/experiment/wrapup.py | 167 +++++++++++++++++++++++++++++++++ 5 files changed, 622 insertions(+) create mode 100644 src/experiment/globals.py create mode 100644 src/experiment/run.py create mode 100644 src/experiment/setup.py create mode 100644 src/experiment/utils.py create mode 100644 src/experiment/wrapup.py diff --git a/src/experiment/globals.py b/src/experiment/globals.py new file mode 100644 index 0000000..2e2e784 --- /dev/null +++ b/src/experiment/globals.py @@ -0,0 +1,47 @@ +"""Centralised experimental parameters and constants. + +This module serves as the single source of truth for all experimental variables. +By centralising parameters here, we ensure consistency across the experiment +and enable easy tracking of what parameters were used in each session. + +Best Practices: + - All experimental parameters should be defined here, not scattered across modules + - Use UPPERCASE naming for all constants (following software engineering best practices) + - Keep parameters immutable - do not modify them during runtime + - During setup, this file is copied to the experiment data folder for reproducibility + - Group related parameters together with clear section organisation + +Example Usage: + ```python + from .globals import BASE_PATH, EXAMPLE_DEVICE_PORT, EXAMPLE_BASELINE_VALUE + + data_folder = BASE_PATH / "data" / "raw" + device = initialise_device(EXAMPLE_DEVICE_PORT) + set_value(EXAMPLE_BASELINE_VALUE) + ``` + +Reproducibility: + The setup.py module automatically copies this file to the experiment data folder + as 'copy_globals.py', allowing you to track exactly which parameters were used + for each experimental session. +""" +from pathlib import Path +# Paths +EXAMPLE_PATH = "Users/username/Documents/data/raw" +EXAMPLE_RAW_PATH = Path(EXAMPLE_PATH) / "raw" +EXAMPLE_PROCESSED_PATH = Path(EXAMPLE_PATH) / "processed" +EXAMPLE_ANALYZED_PATH = Path(EXAMPLE_PATH) / "analyzed" +EXAMPLE_EXTERNAL_PATH = Path(EXAMPLE_PATH) / "external" +EXAMPLE_OTHER_PATH = Path(EXAMPLE_PATH) / "other" + +# Variables DEVICE 1 (example) +EXAMPLE_DEVICE_1_STRING = 'example_value' +EXAMPLE_DEVICE_1_FLOAT = 32.5 +EXAMPLE_DEVICE_1_INTEGER = 45 +EXAMPLE_DEVICE_1_BOOLEAN = True + +# Variables DEVICE 2 (example) +EXAMPLE_DEVICE_2_STRING = 'example_value' +EXAMPLE_DICTIONARY = {'key1': 'value1', 'key2': 42} +EXAMPLE_LIST = [1, 2, 3, 4, 5] +EXAMPLE_NONE = None \ No newline at end of file diff --git a/src/experiment/run.py b/src/experiment/run.py new file mode 100644 index 0000000..6f34244 --- /dev/null +++ b/src/experiment/run.py @@ -0,0 +1,191 @@ +"""Experiment entry point and main execution flow. + +This module serves as the main entry point for running experiments. It orchestrates +the complete experiment workflow: setup, execution, and wrapup. + +Best Practices: + - This should be the only script that users run directly (e.g., python -m src.experiment.run) + - Use argparse for command-line argument parsing if needed + - Implement signal handling for graceful shutdown (Ctrl+C) + - Separate experiment logic into distinct functions that can be tested independently + - Always call setup() before experiment execution + - Always call wrapup() after experiment completion, even on errors (use try/finally) + +Structure: + - main(): Entry point that parses arguments and coordinates the workflow + - experiment(): Core experiment logic (can be split into multiple functions) + - signal_handler(): Handles interruption signals gracefully + +Example Usage: + ```bash + python -m src.experiment.run + ``` + +Example Workflow: + ```python + def main(): + folder_path = create_data_folder() + hardware = setup(folder_path) + + try: + experiment(folder_path, hardware) + finally: + wrapup(folder_path) + cleanup(hardware) + ``` +""" + +import signal +import sys +import time +from pathlib import Path +from typing import Optional + +from rich.console import Console + +from .globals import BASE_PATH +from .setup import setup +from .wrapup import wrapup + +console = Console() + + +def copy_globals_to_experiment_folder(folder_path: Path) -> None: + """Copy globals.py to experiment folder for parameter tracking. + + This ensures that the exact experimental parameters used in each session + are preserved with the data, enabling full reproducibility. + + Args: + folder_path: Path to the experiment data folder. + """ + import shutil + + try: + globals_source = Path(__file__).parent / "globals.py" + globals_dest = folder_path / "copy_globals.py" + + if globals_source.exists(): + shutil.copy2(globals_source, globals_dest) + console.print( + f"✅ Saved experiment parameters to {globals_dest}", + style="green" + ) + else: + console.print( + "[Warning] globals.py not found, skipping copy", + style="yellow" + ) + except Exception as e: + console.print( + f"[Warning] Failed to copy globals.py: {e}", + style="yellow" + ) + + +def signal_handler(sig: int, frame, hardware: Optional[object]) -> None: + """Handle interruption signals gracefully. + + Args: + sig: Signal number. + frame: Current stack frame. + hardware: Hardware object to clean up, if any. + """ + console.print("\n[Signal] Ctrl+C detected. Forcing exit.") + if hardware is not None: + if hasattr(hardware, 'stop'): + hardware.stop() + if hasattr(hardware, 'close'): + hardware.close() + sys.exit(0) + + +def experiment(folder_path: Path, hardware: Optional[object]) -> None: + """Execute the main experiment workflow. + + This function contains the core experiment logic. It should be organised + into clear sections that can be easily understood and modified. + + Args: + folder_path: Path to the experiment data folder. + hardware: Initialised hardware objects from setup. + + Example: + ```python + def experiment(folder_path, hardware): + collect_participant_info(folder_path) + + run_training_phase(folder_path, hardware) + run_experimental_phase(folder_path, hardware) + + save_experiment_metadata(folder_path) + ``` + """ + start_time = time.time() + + console.print("Starting experiment") + console.print(f"Data folder: {folder_path}") + + try: + console.print("Collecting participant information...") + + console.print("Running training phase...") + + console.print("Running experimental phase...") + + end_time = time.time() + duration = end_time - start_time + + duration_file = folder_path / "experiment_duration.txt" + with open(duration_file, "w") as f: + f.write(f"{duration:.2f} seconds\n") + + console.print(f"Experiment completed in {duration:.2f} seconds") + + except Exception as e: + console.print(f"[Error] Experiment failed: {e}", style="red") + raise + + +def main() -> None: + """Main entry point for the experiment. + + Sets up the experiment environment, runs the experiment, and handles cleanup. + """ + import datetime + current_date = datetime.datetime.now().strftime('%Y%m%d') + folder_name = f"{current_date}_experiment" + folder_path = BASE_PATH / "data" / "raw" / folder_name + folder_path.mkdir(parents=True, exist_ok=True) + + copy_globals_to_experiment_folder(folder_path) + + hardware = None + try: + hardware = setup() + + signal.signal( + signal.SIGINT, + lambda sig, frame: signal_handler(sig, frame, hardware) + ) + + experiment(folder_path, hardware) + + except KeyboardInterrupt: + console.print("\n[Interrupt] Experiment interrupted by user", style="yellow") + except Exception as e: + console.print(f"\n[Error] Experiment failed: {e}", style="red") + raise + finally: + if hardware is not None: + if hasattr(hardware, 'stop'): + hardware.stop() + if hasattr(hardware, 'close'): + hardware.close() + + wrapup(folder_path) + + +if __name__ == "__main__": + main() + diff --git a/src/experiment/setup.py b/src/experiment/setup.py new file mode 100644 index 0000000..2e73759 --- /dev/null +++ b/src/experiment/setup.py @@ -0,0 +1,144 @@ +"""Pre-experiment initialisation and validation checks. + +This module handles all pre-experiment setup tasks including hardware checks, +software validation, system resource verification, and experimenter reminders. + +Best Practices: + - Always run setup() at the beginning of every experiment + - Check hardware connections and functionality before starting + - Verify software dependencies and versions + - Check disk space and system resources + - Provide clear reminders and prompts for the experimenter + - Return initialised hardware objects for use in the experiment + - Implement retry logic for hardware checks that may fail intermittently + +Structure: + - setup(): Main setup function that orchestrates all checks + - check_hardware_*(): Individual hardware validation functions + - check_software_*(): Software and dependency checks + - check_system_*(): System resource checks + +Example Usage: + ```python + from .setup import setup + + hardware = setup() + chiller = hardware['chiller'] + camera = hardware['camera'] + ``` + +Example Hardware Check: + ```python + def check_chiller(port: str) -> Optional[Chiller]: + "Check chiller connection and functionality." + try: + chiller = Chiller(port) + chiller.start() + temp = chiller.get_temperature() + console.print(f"Chiller connected. Current temperature: {temp}°C") + return chiller + except Exception as e: + console.print(f"Chiller check failed: {e}") + return None + ``` +""" + +from typing import Dict, Optional, Any + +from rich.console import Console +from rich.panel import Panel + +from .globals import LOCAL_THRESHOLD_GB + +console = Console() + + +def check_disk_space(path: str = ".") -> None: + """Check available disk space before starting experiment. + + Args: + path: Path to check disk space (default: current directory). + """ + import shutil + _, _, free = shutil.disk_usage(path) + free_gb = free / (1024 ** 3) + if free_gb < LOCAL_THRESHOLD_GB: + console.print( + f"[bold yellow]WARNING:[/bold yellow] " + f"Only {free_gb:.2f} GB free. Please ensure sufficient space.", + style="yellow" + ) + + +def check_hardware_example() -> bool: + """Example hardware check function. + + This is a template for implementing hardware checks. Replace with + actual hardware validation logic for your specific equipment. + + Returns: + True if hardware check passes, False otherwise. + """ + console.rule("[bold cyan]Hardware Check Example[/]") + console.print( + Panel( + "This is an example hardware check.\n" + "Replace this function with actual hardware validation.", + border_style="yellow", + ) + ) + + response = input("\nIs the hardware ready? (y/n): ") + return response.lower() == 'y' + + +def setup() -> Optional[Dict[str, Any]]: + """Run all pre-experiment setup checks and initialisation. + + This function orchestrates all setup tasks: + 1. System and dependency checks + 2. Hardware validation + 3. Disk space verification + 4. Initialising hardware objects + + Returns: + Dictionary of initialised hardware objects, or None if setup fails. + + Example: + ```python + hardware = setup() + if hardware: + chiller = hardware.get('chiller') + camera = hardware.get('camera') + ``` + """ + console.rule("[bold cyan]Experiment Setup[/]") + + check_disk_space() + + console.print( + Panel( + "Please verify the following before continuing:\n" + "1. All hardware is properly connected\n" + "2. Software is running correctly\n" + "3. Environment conditions are appropriate", + border_style="cyan", + title="Pre-Experiment Checklist" + ) + ) + input("\nPress Enter when ready to continue...") + + hardware_ready = check_hardware_example() + if not hardware_ready: + console.print( + "[Error] Hardware check failed. Please verify connections.", + style="red" + ) + return None + + hardware = {} + + console.rule("[bold green]Setup Complete[/]") + console.print("✅ All setup checks passed", style="green") + + return hardware if hardware else None diff --git a/src/experiment/utils.py b/src/experiment/utils.py new file mode 100644 index 0000000..2b1c88e --- /dev/null +++ b/src/experiment/utils.py @@ -0,0 +1,73 @@ +"""Utility functions for experiment management. + +This module should contain common utility functions used throughout the experiment +for file operations, console output, path handling, and system information. + +Best Practices: + - Keep utility functions pure and reusable + - Use type hints for all function parameters and return values + - Provide clear docstrings explaining purpose and usage + - Handle errors gracefully with informative messages + - Use rich library for formatted console output + +Common Utilities You Should Implement: + - File and folder operations (create folders, save files, read files) + - Path manipulation and validation + - System information gathering (device name, username, disk space) + - Data validation helpers + - Date/time formatting + +Example Functions Provided: + - example_get_device_name(): Get hostname/device name + - example_get_username(): Get current system username + +Example Usage: + ```python + from rich.console import Console + from .utils import example_get_device_name, example_get_username + + console = Console() + console.rule("[bold cyan]Section Title[/]") + console.print("Message", style="green") + + device = example_get_device_name() + username = example_get_username() + ``` +""" + +from rich.console import Console + +console = Console() + + +def example_get_device_name() -> str: + """Example: Get the hostname/device name of the current machine. + + This is an example function. Replace with your own implementation. + + Returns: + Hostname string, or 'unknown' if unavailable. + """ + import socket + + try: + hostname = socket.gethostname() or socket.getfqdn() + return hostname if hostname else "unknown" + except Exception: + return "unknown" + + +def example_get_username() -> str: + """Example: Get the current system username. + + This is an example function. Replace with your own implementation. + + Returns: + Username string, or 'unknown' if unavailable. + """ + import getpass + + try: + return getpass.getuser() or "unknown" + except Exception: + return "unknown" diff --git a/src/experiment/wrapup.py b/src/experiment/wrapup.py new file mode 100644 index 0000000..5e69409 --- /dev/null +++ b/src/experiment/wrapup.py @@ -0,0 +1,167 @@ +"""Post-experiment cleanup and finalisation. + +This module handles all tasks that should be performed after experiment completion, +including data validation, final data collection, result generation, and cleanup. + +Best Practices: + - Always call wrapup() at the end of the experiment (use try/finally) + - Validate data completeness and quality before finishing + - Collect any final measurements or metadata + - Generate summary plots or reports + - Copy data to backup locations (cloud, research drive, etc.) + - Clean up hardware connections and resources + - Provide clear feedback to the experimenter about completion status + +Structure: + - wrapup(): Main wrapup function that orchestrates all cleanup tasks + - validate_data_files(): Check that all required data files exist + - collect_final_metadata(): Gather any remaining experiment information + - run_sanity_checks(): Automatic code to do sanity checks and summarise results + +Example Usage: + ```python + from .wrapup import wrapup + + try: + run_experiment() + finally: + wrapup(folder_path=Path("data/raw/participant_001")) + ``` +""" + +from pathlib import Path +from typing import Dict, List + +from rich.console import Console + +console = Console() + + +def validate_data_files(folder_path: Path, required_files: List[str]) -> Dict[str, bool]: + """Validate that all required data files exist. + + This function should check that all necessary data files are present + in the experiment folder. Return a dictionary mapping filenames to + their existence status. + + Args: + folder_path: Path to experiment data folder. + required_files: List of required filenames to check. + + Returns: + Dictionary mapping filenames to existence status (True/False). + + Example: + ```python + required = ['trials.csv', 'metadata.json', 'copy_globals.py'] + validation = validate_data_files(folder_path, required) + all_present = all(validation.values()) + ``` + """ + console.print(f"Validating data files in {folder_path}") + + results = {} + for filename in required_files: + file_path = folder_path / filename + results[filename] = file_path.exists() + + return results + + +def collect_final_metadata(folder_path: Path) -> None: + """Collect any final metadata or measurements. + + This function should prompt for any remaining information that needs + to be recorded at the end of the experiment. + + Args: + folder_path: Path to experiment data folder. + """ + console.rule("[bold cyan]Final Metadata Collection[/]") + + console.print(f"Collecting final metadata for {folder_path}") + + console.print( + "Please provide any final information about the experiment session.", + style="cyan" + ) + + input("\nPress Enter to continue...") + + +def run_sanity_checks(folder_path: Path) -> None: + """Run automatic sanity checks and summarise results. + + This function should perform automatic validation of data quality, + completeness, and consistency. Use your functions or code to perform + sanity checks and summarise the results. + + Args: + folder_path: Path to experiment data folder. + """ + console.rule("[bold cyan]Running Sanity Checks[/]") + + console.print(f"Running sanity checks on data in {folder_path}") + + console.print("Use your functions or code to perform sanity checks here.") + + +def backup_data(folder_path: Path) -> None: + """Back up experiment data to research drive or cloud storage. + + This function should copy the experiment data folder to a backup location. + Use your functions or code to back up your data. + + Args: + folder_path: Path to experiment data folder. + """ + console.rule("[bold cyan]Backing Up Data[/]") + + console.print(f"Copying data from {folder_path}") + + console.print("Use your functions or code to back up your data here.") + + +def wrapup(folder_path: Path) -> None: + """Handle end-of-experiment wrap-up tasks. + + This function orchestrates all post-experiment tasks: + 1. Validate that all required data files exist + 2. Collect final metadata + 3. Run sanity checks and summarise results + 4. Back up data to research drive or cloud storage + + Args: + folder_path: Path to experiment data folder. + + Example: + ```python + try: + run_experiment() + finally: + wrapup(folder_path=experiment_folder) + ``` + """ + console.rule("[bold cyan]Experiment Wrap-Up[/]") + + if not folder_path.exists(): + console.print( + f"[Warning] Experiment folder not found: {folder_path}", + style="yellow" + ) + return + + required_files = ['copy_globals.py'] + validate_data_files(folder_path, required_files) + + collect_final_metadata(folder_path) + + run_sanity_checks(folder_path) + + backup_data(folder_path) + + console.rule("[bold green]Wrap-Up Complete[/]") + console.print( + f"✅ Experiment wrap-up completed for {folder_path.name}", + style="green" + ) From d1288078fc82d938a16982651f7a45bee5c819ba Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 27 Nov 2025 11:44:29 +0100 Subject: [PATCH 2/2] Refactor experiment logic by removing unnecessary hardware handling in signal_handler and simplifying the experiment function. Update setup checks to include retry logic with error handling for hardware and software validations. --- src/experiment/experiment.py | 88 ++++++++++++++++++++++ src/experiment/run.py | 77 +++---------------- src/experiment/setup.py | 138 ++++++++++++++++++++++++++--------- 3 files changed, 205 insertions(+), 98 deletions(-) create mode 100644 src/experiment/experiment.py diff --git a/src/experiment/experiment.py b/src/experiment/experiment.py new file mode 100644 index 0000000..ff7a850 --- /dev/null +++ b/src/experiment/experiment.py @@ -0,0 +1,88 @@ +"""Core experiment execution logic. + +This module contains the main experiment workflow functions that execute +the experimental procedures, collect data, and manage the experiment flow. + +Best Practices: + - Keep experiment logic separate from setup and wrapup + - Organise experiment into clear phases (training, experimental, etc.) + - Handle errors gracefully and save progress when possible + - Log important events and data collection points + - Use clear function names that describe what each phase does + - Different sections should generally be written in separate scripts/modules + that are imported here (e.g., training_staircase.py, experiment_staircase.py) + rather than implementing everything directly in this file + +Structure: + - experiment(): Main experiment function that orchestrates the workflow + - Import functions from separate modules for each phase (e.g., from .training import training_phase) + +Example Usage: + ```python + from .experiment import experiment + + experiment(folder_path=Path("data/raw/participant_001")) + ``` +""" + +import time +from pathlib import Path + +from rich.console import Console + +console = Console() + + +def experiment(folder_path: Path) -> None: + """Execute the main experiment workflow. + + This function contains the core experiment logic. It should be organised + into clear sections that can be easily understood and modified. + + Note: Different sections should generally be written in separate scripts/modules + that are imported here (e.g., from .training_staircase import training_staircase) + rather than implementing everything directly in this file. This keeps the code + modular, testable, and easier to maintain. + + Args: + folder_path: Path to the experiment data folder. + + Example: + ```python + from .training_staircase import training_staircase + from .experiment_staircase import experiment_staircase + + def experiment(folder_path): + collect_participant_info(folder_path) + + training_staircase(folder_path, ...) + experiment_staircase(folder_path, ...) + + save_experiment_metadata(folder_path) + ``` + """ + start_time = time.time() + + console.print("Starting experiment") + console.print(f"Data folder: {folder_path}") + + try: + console.print("Collecting participant information...") + + console.print("Running training phase...") + + console.print("Running experimental phase...") + + end_time = time.time() + duration = end_time - start_time + + duration_file = folder_path / "experiment_duration.txt" + with open(duration_file, "w") as f: + f.write(f"{duration:.2f} seconds\n") + + console.print(f"Experiment completed in {duration:.2f} seconds") + + except Exception as e: + console.print(f"[Error] Experiment failed: {e}", style="red") + raise + diff --git a/src/experiment/run.py b/src/experiment/run.py index 6f34244..69de378 100644 --- a/src/experiment/run.py +++ b/src/experiment/run.py @@ -13,7 +13,6 @@ Structure: - main(): Entry point that parses arguments and coordinates the workflow - - experiment(): Core experiment logic (can be split into multiple functions) - signal_handler(): Handles interruption signals gracefully Example Usage: @@ -37,12 +36,11 @@ def main(): import signal import sys -import time from pathlib import Path -from typing import Optional from rich.console import Console +from .experiment import experiment from .globals import BASE_PATH from .setup import setup from .wrapup import wrapup @@ -83,68 +81,22 @@ def copy_globals_to_experiment_folder(folder_path: Path) -> None: ) -def signal_handler(sig: int, frame, hardware: Optional[object]) -> None: +def signal_handler(sig: int, frame) -> None: """Handle interruption signals gracefully. + Stops any running hardware devices and software processes before exiting. + This ensures clean shutdown and prevents hardware from being left in an + unsafe state. + Args: sig: Signal number. frame: Current stack frame. - hardware: Hardware object to clean up, if any. """ console.print("\n[Signal] Ctrl+C detected. Forcing exit.") - if hardware is not None: - if hasattr(hardware, 'stop'): - hardware.stop() - if hasattr(hardware, 'close'): - hardware.close() - sys.exit(0) - - -def experiment(folder_path: Path, hardware: Optional[object]) -> None: - """Execute the main experiment workflow. - - This function contains the core experiment logic. It should be organised - into clear sections that can be easily understood and modified. - - Args: - folder_path: Path to the experiment data folder. - hardware: Initialised hardware objects from setup. - - Example: - ```python - def experiment(folder_path, hardware): - collect_participant_info(folder_path) - - run_training_phase(folder_path, hardware) - run_experimental_phase(folder_path, hardware) - - save_experiment_metadata(folder_path) - ``` - """ - start_time = time.time() - console.print("Starting experiment") - console.print(f"Data folder: {folder_path}") + console.print("Stop any running hardware devices and software processes here.") - try: - console.print("Collecting participant information...") - - console.print("Running training phase...") - - console.print("Running experimental phase...") - - end_time = time.time() - duration = end_time - start_time - - duration_file = folder_path / "experiment_duration.txt" - with open(duration_file, "w") as f: - f.write(f"{duration:.2f} seconds\n") - - console.print(f"Experiment completed in {duration:.2f} seconds") - - except Exception as e: - console.print(f"[Error] Experiment failed: {e}", style="red") - raise + sys.exit(0) def main() -> None: @@ -160,16 +112,15 @@ def main() -> None: copy_globals_to_experiment_folder(folder_path) - hardware = None try: - hardware = setup() + setup() signal.signal( signal.SIGINT, - lambda sig, frame: signal_handler(sig, frame, hardware) + lambda sig, frame: signal_handler(sig, frame) ) - experiment(folder_path, hardware) + experiment(folder_path) except KeyboardInterrupt: console.print("\n[Interrupt] Experiment interrupted by user", style="yellow") @@ -177,11 +128,7 @@ def main() -> None: console.print(f"\n[Error] Experiment failed: {e}", style="red") raise finally: - if hardware is not None: - if hasattr(hardware, 'stop'): - hardware.stop() - if hasattr(hardware, 'close'): - hardware.close() + console.print("Stop any running hardware devices and software processes here.") wrapup(folder_path) diff --git a/src/experiment/setup.py b/src/experiment/setup.py index 2e73759..7023dcf 100644 --- a/src/experiment/setup.py +++ b/src/experiment/setup.py @@ -10,7 +10,10 @@ - Check disk space and system resources - Provide clear reminders and prompts for the experimenter - Return initialised hardware objects for use in the experiment - - Implement retry logic for hardware checks that may fail intermittently + - Implement retry logic using while loops - if a check fails or user says no, + provide the opportunity to address the issue and try again + - Use try/except blocks around hardware checks to handle errors gracefully + and allow retries when hardware connections fail Structure: - setup(): Main setup function that orchestrates all checks @@ -18,35 +21,14 @@ - check_software_*(): Software and dependency checks - check_system_*(): System resource checks -Example Usage: - ```python - from .setup import setup - - hardware = setup() - chiller = hardware['chiller'] - camera = hardware['camera'] - ``` - -Example Hardware Check: - ```python - def check_chiller(port: str) -> Optional[Chiller]: - "Check chiller connection and functionality." - try: - chiller = Chiller(port) - chiller.start() - temp = chiller.get_temperature() - console.print(f"Chiller connected. Current temperature: {temp}°C") - return chiller - except Exception as e: - console.print(f"Chiller check failed: {e}") - return None - ``` +Below is an example of how to use the setup function to check the hardware. """ from typing import Dict, Optional, Any from rich.console import Console from rich.panel import Panel +from rich.prompt import Confirm from .globals import LOCAL_THRESHOLD_GB @@ -75,6 +57,7 @@ def check_hardware_example() -> bool: This is a template for implementing hardware checks. Replace with actual hardware validation logic for your specific equipment. + Use try/except blocks to handle connection errors gracefully. Returns: True if hardware check passes, False otherwise. @@ -88,8 +71,66 @@ def check_hardware_example() -> bool: ) ) - response = input("\nIs the hardware ready? (y/n): ") - return response.lower() == 'y' + try: + response = Confirm.ask("\nIs the hardware ready?", default=True) + return response + except Exception as e: + console.print(f"[Error] Hardware check failed: {e}", style="red") + return False + + +def check_software_example() -> bool: + """Example software check function. + + This is a template for implementing software checks. Replace with + actual software validation logic for your specific software. + Use try/except blocks to handle errors gracefully. + + Returns: + True if software check passes, False otherwise. + """ + console.rule("[bold cyan]Software Check Example[/]") + console.print( + Panel( + "This is an example software check.\n" + "Replace this function with actual software validation.", + border_style="yellow", + ) + ) + + try: + response = Confirm.ask("\nIs the software ready?", default=True) + return response + except Exception as e: + console.print(f"[Error] Software check failed: {e}", style="red") + return False + + +def check_system_example() -> bool: + """Example system check function. + + This is a template for implementing system checks. Replace with + actual system validation logic for your specific system. + Use try/except blocks to handle errors gracefully. + + Returns: + True if system check passes, False otherwise. + """ + console.rule("[bold cyan]System Check Example[/]") + console.print( + Panel( + "This is an example system check.\n" + "Replace this function with actual system validation.", + border_style="yellow", + ) + ) + + try: + response = Confirm.ask("\nIs the system ready?", default=True) + return response + except Exception as e: + console.print(f"[Error] System check failed: {e}", style="red") + return False def setup() -> Optional[Dict[str, Any]]: @@ -101,6 +142,9 @@ def setup() -> Optional[Dict[str, Any]]: 3. Disk space verification 4. Initialising hardware objects + All checks are wrapped in while loops with try/except blocks to allow + retries if checks fail or user indicates issues need to be addressed. + Returns: Dictionary of initialised hardware objects, or None if setup fails. @@ -126,16 +170,44 @@ def setup() -> Optional[Dict[str, Any]]: title="Pre-Experiment Checklist" ) ) - input("\nPress Enter when ready to continue...") - hardware_ready = check_hardware_example() - if not hardware_ready: - console.print( - "[Error] Hardware check failed. Please verify connections.", - style="red" - ) + ready = Confirm.ask("\nAre you ready to continue?", default=True) + if not ready: + console.print("Setup cancelled by user.", style="yellow") return None + while True: + try: + hardware_ready = check_hardware_example() + if hardware_ready: + break + else: + console.print( + "[Warning] Hardware check did not pass.", + style="yellow" + ) + retry = Confirm.ask( + "Would you like to run the hardware check again?", + default=True + ) + if not retry: + console.print( + "[Error] Hardware check failed. Please verify connections.", + style="red" + ) + return None + except Exception as e: + console.print( + f"[Error] Hardware check encountered an error: {e}", + style="red" + ) + retry = Confirm.ask( + "Would you like to try the hardware check again?", + default=True + ) + if not retry: + return None + hardware = {} console.rule("[bold green]Setup Complete[/]")