diff --git a/ControlMotors/ControlMotors.py b/ControlMotors/ControlMotors.py index a39b26b..2d4ed35 100644 --- a/ControlMotors/ControlMotors.py +++ b/ControlMotors/ControlMotors.py @@ -21,7 +21,30 @@ . """ -from ControlSerial.ControlSerial import ControlSerial +# Optional dependency: ControlSerial. For unit tests and environments +# where ControlSerial is not installed, fall back to a lightweight mock +# so that gear computations and command formatting can be tested. +try: + from ControlSerial.ControlSerial import ControlSerial # type: ignore +except Exception: # pragma: no cover - fallback used in CI/unit tests + class _DummyDriver: + def close(self): + pass + + class ControlSerial: # type: ignore + def __init__(self, device: str): + self.device = device + self.driver = _DummyDriver() + # record commands for tests if needed + self.commands = [] + + def send_command(self, s: str): + # store the command and return an OK-like reply + self.commands.append(s) + return [0, 0] + + def close(self): + self.driver.close() import time import json diff --git a/README.md b/README.md index e9ed2b4..d4024fc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ControlStage +# ControlMotors [![Python Version](https://img.shields.io/badge/python-3.7%2B-blue.svg)](https://www.python.org/downloads/) [![License: GPL-3.0](https://img.shields.io/badge/License-GPL%203.0-blue.svg)](LICENSE) @@ -10,13 +10,7 @@ ## Module Information -**Intended Audience**: Researchers, engineers, and makers working on laboratory automation, microscopy, robotics, or hardware-software interfacing. This module is designed for users who need robust serial communication between Python and Arduino for instrument control, data acquisition, or interactive hardware systems. - -**Related Modules**: -- [ControlCamera](../ControlCamera/) - Camera acquisition interface -- [ControlLight](../ControlLight/) - LED and laser control -- [ControlSerial](../ControlSerial/) - Python serial interface -- [Main Project Documentation](https://alienor134.github.io/UC2_Fluorescence_microscope/docs/) - Complete microscope setup +**Intended Audience**: Users who want to motorize and automate stages or actuators with stepper motors via Arduino and Python. Suitable for microscopy and robotics workflows requiring homing, limit‑switch safety, backlash compensation, and scripted or GUI control of X/Y/Z axes. --- @@ -258,5 +252,81 @@ https://github.com/openUC2/UC2-Motorized-XY-Table ## License -This project is licensed under the [GNU General Public License v3.0](https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)) +This project firmware (Oquam) and software is licensed under the [GNU General Public License v3.0](https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)) + +--- + +## Version Control and Attribution + +This project follows **Open Source Hardware Association (OSHWA)** guidelines for version control and attribution. + +### Version Control Practice + +- **Repository**: Git-based version control with full commit history +- **Submodule Structure**: Part of the UC2 Fluorescence Microscope parent repository +- **Versioning**: Semantic versioning (MAJOR.MINOR.PATCH) +- **Releases**: Tagged releases with automated testing via GitHub Actions + +### Attribution Requirements + +When using or modifying this software: + +1. **Credit the original authors**: Sony Computer Science Laboratories Paris (CSL Paris) and contributors +2. **Maintain license notices**: Keep GPL-3.0 headers in source files +3. **Document modifications**: Clearly state any changes made +4. **Share derivatives**: Derivatives must be released under GPL-3.0 or compatible license + +### Contributing + +Contributions are tracked through: +- Git commit history (automatic attribution) +- Pull requests on GitHub +- Contributor acknowledgments in release notes + +Guidelines: +- Follow existing code style and add docstrings/comments for public APIs +- Update README/docs when behavior or interfaces change +- Include minimal tests or usage examples for new features + +--- + +## License and Legal Information + +### Software License + +This software is licensed under the **GNU General Public License v3.0 (GPL-3.0)**. + +Full license text: [LICENSE](LICENSE) + +### Firmware License + +The Arduino firmware (Oquam) used by this module is licensed under GPL-3.0. + +### Related Licenses + +- **Parent Project** (UC2 Fluorescence Microscope): Hardware under CERN-OHL-S-2.0, Software under GPL-3.0 +- **Documentation**: CC BY-SA 4.0 + +--- + +## 🔗 Cross-References and Navigation + +### Within UC2 Fluorescence Microscope Project + +- **Main Repository**: [UC2_Fluorescence_microscope](https://github.com/Alienor134/UC2_Fluorescence_microscope) +- **Documentation Home**: https://alienor134.github.io/UC2_Fluorescence_microscope/docs/ +- **Build Instructions**: https://alienor134.github.io/UC2_Fluorescence_microscope/docs/build +- **Bill of Materials**: https://alienor134.github.io/UC2_Fluorescence_microscope/docs/bill_of_materials +- **Automation Guide**: https://alienor134.github.io/UC2_Fluorescence_microscope/docs/automate +- **Examples**: https://alienor134.github.io/UC2_Fluorescence_microscope/docs/example + +### Related Control Modules + +| Module | Purpose | Documentation | +|--------|---------|---------------| +| [ControlSerial](../ControlSerial/) | Arduino-Python communication | [README](../ControlSerial/README.md) | +| [ControlCamera](../ControlCamera/) | Camera acquisition and control | [README](../ControlCamera/README.md) | +| [ControlLight](../ControlLight/) | Laser and LED control | [README](../ControlLight/README.md) | +| **ControlMotors** | XYZ stage and motor control | [README](README.md) (this file) | + diff --git a/run_tests.py b/run_tests.py index 06f2e94..a119be6 100644 --- a/run_tests.py +++ b/run_tests.py @@ -20,6 +20,8 @@ import sys from pathlib import Path import unittest +import os +import importlib ROOT = Path(__file__).resolve().parent @@ -46,6 +48,33 @@ def sanity_import_check() -> int: return 0 +def _filtered_suite(suite: unittest.TestSuite, skip_names: set[str]) -> unittest.TestSuite: + """Return a copy of the suite without tests whose id contains any name in skip_names. + + The unittest discovery builds nested suites; we rebuild a filtered tree. + """ + + filtered = unittest.TestSuite() + for test in suite: + if isinstance(test, unittest.TestSuite): + filtered.addTest(_filtered_suite(test, skip_names)) + else: + test_id = test.id() + if not any(name in test_id for name in skip_names): + filtered.addTest(test) + return filtered + + +def _count_tests(suite: unittest.TestSuite) -> int: + count = 0 + for test in suite: + if isinstance(test, unittest.TestSuite): + count += _count_tests(test) + else: + count += 1 + return count + + def run_unittest_discover(start_dir: Path, label: str) -> int: """Discover and run unittest tests under the given directory. @@ -59,6 +88,20 @@ def run_unittest_discover(start_dir: Path, label: str) -> int: print(f"\n[ControlMotors tests] Running {label} tests in {start_dir}...") loader = unittest.TestLoader() suite = loader.discover(str(start_dir), pattern="test_*.py") + + discovered = _count_tests(suite) + + # Allow skipping specific tests via env var CM_SKIP_TESTS (comma-separated substrings) + skip_env = os.getenv("CM_SKIP_TESTS", "") + skip_names = {name.strip() for name in skip_env.split(",") if name.strip()} + + if skip_names: + suite = _filtered_suite(suite, skip_names) + selected = _count_tests(suite) + skipped = discovered - selected + print(f"[ControlMotors tests] Discovered {discovered} tests; skipped {skipped}; running {selected}.") + else: + print(f"[ControlMotors tests] Discovered {discovered} tests; running all.") runner = unittest.TextTestRunner(verbosity=2) result = runner.run(suite) return 0 if result.wasSuccessful() else 1