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
2 changes: 1 addition & 1 deletion .github/workflows/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.14'
python-version: "3.14"

- name: Install dependencies
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
python-version: ["3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
Expand All @@ -38,4 +38,4 @@ jobs:
pip install tox
- name: Test with tox
run: |
tox -epy
tox -epy -elint -etype
107 changes: 97 additions & 10 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*.py[codz]
*$py.class

# C extensions
Expand All @@ -20,19 +20,17 @@ parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
!pyinstaller.spec

# Installer logs
pip-log.txt
Expand All @@ -48,9 +46,10 @@ htmlcov/
nosetests.xml
coverage.xml
*.cover
*.py,cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
Expand All @@ -73,6 +72,7 @@ instance/
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
Expand All @@ -83,27 +83,73 @@ profile_default/
ipython_config.py

# pyenv
.python-version
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
# Pipfile.lock

# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# uv.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
# poetry.lock
# poetry.toml

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
# pdm.lock
# pdm.toml
.pdm-python
.pdm-build/

# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
# pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# Redis
*.rdb
*.aof
*.pid

# RabbitMQ
mnesia/
rabbitmq/
rabbitmq-data/

# ActiveMQ
activemq-data/

# SageMath parsed files
*.sage.py

# Environments
.env
.envrc
.venv
env/
venv/
Expand All @@ -129,3 +175,44 @@ dmypy.json
# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
# .idea/

# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/

# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Temporary file for partial code execution
tempCodeRunnerFile.py

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/

# Streamlit
.streamlit/secrets.toml
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = [
]
readme = "README.md"
license = {text = "GPLv3"}
requires-python = ">=3.9"
requires-python = ">=3.11"
dependencies = [
"PyVISA",
"PyVISA-py",
Expand All @@ -33,3 +33,6 @@ build-backend = "hatchling.build"

[tool.hatch.version]
path = "src/comet/__init__.py"

[tool.ruff.lint]
extend-ignore = ["BLE001", "RUF022"]
2 changes: 1 addition & 1 deletion src/comet/driver/corvus/venus1.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"'comet.driver.corvus.venus1' is deprecated and will be removed in a future version. "
"Use 'comet.driver.itk.corvustt' or 'comet.driver.smc.corvus' instead.",
DeprecationWarning,
stacklevel=1
stacklevel=1,
)


Expand Down
42 changes: 27 additions & 15 deletions src/comet/driver/cts/itc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import datetime
from collections import namedtuple
from typing import Union
from datetime import UTC, datetime
from typing import ClassVar

from comet.driver import Driver

Expand All @@ -10,7 +10,7 @@
class ITCDriver(Driver):
"""ITC driver base class."""

def query_bytes(self, message: Union[str, bytes], count: int) -> str:
def query_bytes(self, message: str | bytes, count: int) -> str:
"""Raw query for bytes.

>>> instr.query_bytes("P", 4)
Expand All @@ -23,7 +23,7 @@ def query_bytes(self, message: Union[str, bytes], count: int) -> str:


class AnalogChannel(ITCDriver):
CHANNELS: dict[int, bytes] = {
CHANNELS: ClassVar[dict[int, bytes]] = {
1: b"A0",
2: b"A1",
3: b"A2",
Expand Down Expand Up @@ -63,7 +63,9 @@ def __setitem__(self, index: int, value: float) -> None:
"""
if not 1 <= index <= 7:
raise ValueError(f"invalid channel number: {index}")
code = type(self).CHANNELS[index].lower().decode() # write requires lower case 'a'
code = (
type(self).CHANNELS[index].lower().decode()
) # write requires lower case 'a'
result = self.query_bytes(f"{code} {value:05.1f}", 1)
if result != "a":
raise RuntimeError(f"failed to set target for channel {index}")
Expand All @@ -72,7 +74,7 @@ def __setitem__(self, index: int, value: float) -> None:
class ITC(ITCDriver):
"""Interface for CTS Climate Chambers."""

WARNING_MESSAGES: dict[str, str] = {
WARNING_MESSAGES: ClassVar[dict[str, str]] = {
"\x01": "Wassernachfüllen",
"\x02": "Temp. Toleranzband Oben",
"\x03": "Temp. Toleranzband Unten",
Expand All @@ -82,7 +84,7 @@ class ITC(ITCDriver):
}
"""Warning messages."""

ERROR_MESSAGES: dict[str, str] = {
ERROR_MESSAGES: ClassVar[dict[str, str]] = {
"\x31": "Temperatur Grenze Min 08-B1",
"\x32": "Temperatur Grenze Max 08-B1",
"\x33": "Temp. Begrenzer Pruefr. 01-F1.1",
Expand Down Expand Up @@ -119,28 +121,28 @@ def __init__(self, resource) -> None:

def identify(self) -> str:
"""Returns instrument identification."""
self.time # perform device access
_ = self.time # perform device access
return "ITC climate chamber"

@property
def time(self) -> datetime.datetime:
def time(self) -> datetime:
"""Returns current date and time of device as datetime object.

>>> instr.time
datetime.datetime(2019, 6, 12, 13, 01, 21)
"""
result = self.query_bytes("T", 13)
return datetime.datetime.strptime(result, "T%d%m%y%H%M%S")
return datetime.strptime(result, "T%d%m%y%H%M%S").replace(tzinfo=UTC)

@time.setter
def time(self, dt: datetime.datetime) -> None:
def time(self, dt: datetime) -> None:
"""Update device date and time, returns updated data and time as datetime object.

>>> instr.time = datetime.datetime.now()
"""
datetime_format = "t%d%m%y%H%M%S"
result = self.query_bytes(dt.strftime(datetime_format), 13)
if dt != datetime.datetime.strptime(result, datetime_format):
if dt != datetime.strptime(result, datetime_format).replace(tzinfo=UTC):
raise RuntimeError("failed to set date and time")

@property
Expand All @@ -155,10 +157,20 @@ def status(self) -> Status:
result = self.query_bytes("S", 10)
running = bool(int(result[1]))
is_error = bool(int(result[2]))
channels = {channel: bool(int(state)) for channel, state in enumerate(result[3:9])}
channels = {
channel: bool(int(state)) for channel, state in enumerate(result[3:9])
}
error_nr = result[9]
warning = type(self).WARNING_MESSAGES[error_nr] if is_error and error_nr in type(self).WARNING_MESSAGES else None
error = type(self).ERROR_MESSAGES[error_nr] if is_error and error_nr in type(self).ERROR_MESSAGES else None
warning = (
type(self).WARNING_MESSAGES[error_nr]
if is_error and error_nr in type(self).WARNING_MESSAGES
else None
)
error = (
type(self).ERROR_MESSAGES[error_nr]
if is_error and error_nr in type(self).ERROR_MESSAGES
else None
)
return type(self).Status(running, warning, error, channels)

@property
Expand Down
2 changes: 2 additions & 0 deletions src/comet/driver/driver.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from abc import ABC

__all__ = ["Driver"]


class Driver(ABC):
"""Base class for instrument drivers."""
Expand Down
11 changes: 6 additions & 5 deletions src/comet/driver/ers/ac3.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Driver for ECR AC3 thermal chuck"""

from typing import ClassVar

from comet.driver.generic import Instrument, InstrumentError
from typing import Optional

__all__ = ["AC3"]

Expand All @@ -20,7 +21,7 @@ class AC3(Instrument):
STATUS_COOLING: int = 2
STATUS_ERROR: int = 8

ERROR_MESSAGES = {
ERROR_MESSAGES: ClassVar[dict[int, str]] = {
1: "OVERTEMP: The Chuck temperature has passed the maximum temperature limit by more than 2°C.",
3: "CHUCKCABLE: Analog-digital-converter error",
4: "CHUCKCABLE: The Chuck's sensor cable or the Chuck sensor is defective.",
Expand Down Expand Up @@ -55,7 +56,7 @@ def reset(self) -> None: ... # not supported

def clear(self) -> None: ... # not supported

def next_error(self) -> Optional[InstrumentError]:
def next_error(self) -> InstrumentError | None:
code = int(self._query("RE")[1:])

if code:
Expand Down Expand Up @@ -100,7 +101,7 @@ def target_temperature(self, value: float) -> None:
"""Set temperature setpoint in °C."""

if value > 300 or value < -70:
raise ValueError("Temperature {} is out of range -70 to 300C".format(value))
raise ValueError(f"Temperature {value} is out of range -70 to 300C")

# Convert to 1/10°C with sign
temp = int(value * 10)
Expand All @@ -120,7 +121,7 @@ def operating_mode(self) -> int:
@operating_mode.setter
def operating_mode(self, mode: int) -> None:
if mode not in range(1, 5):
raise ValueError("Invalid mode: {}".format(mode))
raise ValueError(f"Invalid mode: {mode}")
"""Set operating mode."""
self._query(f"SO{mode}")

Expand Down
Loading
Loading