Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion ControlMotors/ControlMotors.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,30 @@
<http://www.gnu.org/licenses/>.

"""
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
Expand Down
88 changes: 79 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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.

---

Expand Down Expand Up @@ -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) |


43 changes: 43 additions & 0 deletions run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import sys
from pathlib import Path
import unittest
import os
import importlib

ROOT = Path(__file__).resolve().parent

Expand All @@ -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.

Expand All @@ -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
Expand Down