scietex.hal.qcm 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scietex_hal_qcm-1.0.0/LICENSE +21 -0
- scietex_hal_qcm-1.0.0/PKG-INFO +90 -0
- scietex_hal_qcm-1.0.0/README.md +45 -0
- scietex_hal_qcm-1.0.0/pyproject.toml +52 -0
- scietex_hal_qcm-1.0.0/setup.cfg +4 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/__init__.py +4 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/base/__init__.py +3 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/base/data.py +84 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/base/ftm_calculation.py +21 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/base/ftm_materials_data.py +63 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/base/gated_ftm.py +235 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/base/rs485/__init__.py +3 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/base/rs485/gated_ftm.py +326 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/base/serial/__init__.py +3 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/base/serial/gated_ftm.py +323 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/cyky/__init__.py +3 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/cyky/tm106b.py +411 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/scietex/__init__.py +4 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/scietex/ftm_one.py +480 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/scietex/ftm_one_usb.py +618 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/scietex/utils.py +21 -0
- scietex_hal_qcm-1.0.0/src/scietex/hal/qcm/version.py +8 -0
- scietex_hal_qcm-1.0.0/src/scietex.hal.qcm.egg-info/PKG-INFO +90 -0
- scietex_hal_qcm-1.0.0/src/scietex.hal.qcm.egg-info/SOURCES.txt +26 -0
- scietex_hal_qcm-1.0.0/src/scietex.hal.qcm.egg-info/dependency_links.txt +1 -0
- scietex_hal_qcm-1.0.0/src/scietex.hal.qcm.egg-info/requires.txt +12 -0
- scietex_hal_qcm-1.0.0/src/scietex.hal.qcm.egg-info/top_level.txt +1 -0
- scietex_hal_qcm-1.0.0/tests/test_version.py +47 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Anton Bondarenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scietex.hal.qcm
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Scietex Quatz Microbalance HAL package
|
|
5
|
+
Author-email: Anton Bondarenko <bond.anton@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Anton Bondarenko
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
Project-URL: Homepage, https://github.com/bond-anton/scietex.hal.qcm
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/bond-anton/scietex.hal.qcm/issues
|
|
29
|
+
Classifier: Programming Language :: Python :: 3
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Operating System :: OS Independent
|
|
32
|
+
Requires-Python: >=3.9
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
License-File: LICENSE
|
|
35
|
+
Requires-Dist: msgspec[yaml]~=0.19.0
|
|
36
|
+
Requires-Dist: scietex.hal.serial<2.0.0,>=1.0.5
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Provides-Extra: test
|
|
39
|
+
Requires-Dist: flake8; extra == "test"
|
|
40
|
+
Requires-Dist: pytest; extra == "test"
|
|
41
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
42
|
+
Provides-Extra: lint
|
|
43
|
+
Requires-Dist: pylint; extra == "lint"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# scietex.hal.qcm
|
|
47
|
+
|
|
48
|
+
The `scietex.hal.qcm` package is a Python library for interfacing with Quartz Microbalance (QCM).
|
|
49
|
+
It provides an abstract base class (`QCM`), which in its turn inherits `scietex.hal.serial.RS485Client`.
|
|
50
|
+
The concrete implementations for two models of QCM manufactured by Scietex and CYKY are provided.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
- Abstract base class (`VFD`) for consistent VFD implementations.
|
|
54
|
+
- Implemented VFD models:
|
|
55
|
+
- `Scietex ftmONE`.
|
|
56
|
+
- `CYKY TM106B`.
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
Install the package via pip (assuming it’s published to PyPI):
|
|
60
|
+
```bash
|
|
61
|
+
pip install scietex.hal.qcm
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Alternatively, clone the repository and install locally:
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/bond-anton/scietex.hal.qcm.git
|
|
67
|
+
cd scietex.hal.qcm
|
|
68
|
+
pip install .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Requirements
|
|
72
|
+
|
|
73
|
+
- Python 3.9 or higher.
|
|
74
|
+
- `scietex.hal.serial` (For RS485 communication support).
|
|
75
|
+
|
|
76
|
+
## Contributing
|
|
77
|
+
|
|
78
|
+
Contributions are welcome! To contribute:
|
|
79
|
+
|
|
80
|
+
1. Fork the repository.
|
|
81
|
+
2. Create a feature branch (`git checkout -b feature/YourFeature`).
|
|
82
|
+
3. Commit your changes (`git commit -m "Add your message"`).
|
|
83
|
+
4. Push to the branch (`git push origin feature/YourFeature`).
|
|
84
|
+
5. Open a Pull Request.
|
|
85
|
+
|
|
86
|
+
Please include tests (if applicable) and follow PEP 8 style guidelines.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
This project is licensed under the **MIT License** - see the LICENSE file for details.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# scietex.hal.qcm
|
|
2
|
+
|
|
3
|
+
The `scietex.hal.qcm` package is a Python library for interfacing with Quartz Microbalance (QCM).
|
|
4
|
+
It provides an abstract base class (`QCM`), which in its turn inherits `scietex.hal.serial.RS485Client`.
|
|
5
|
+
The concrete implementations for two models of QCM manufactured by Scietex and CYKY are provided.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
- Abstract base class (`VFD`) for consistent VFD implementations.
|
|
9
|
+
- Implemented VFD models:
|
|
10
|
+
- `Scietex ftmONE`.
|
|
11
|
+
- `CYKY TM106B`.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
Install the package via pip (assuming it’s published to PyPI):
|
|
15
|
+
```bash
|
|
16
|
+
pip install scietex.hal.qcm
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Alternatively, clone the repository and install locally:
|
|
20
|
+
```bash
|
|
21
|
+
git clone https://github.com/bond-anton/scietex.hal.qcm.git
|
|
22
|
+
cd scietex.hal.qcm
|
|
23
|
+
pip install .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Python 3.9 or higher.
|
|
29
|
+
- `scietex.hal.serial` (For RS485 communication support).
|
|
30
|
+
|
|
31
|
+
## Contributing
|
|
32
|
+
|
|
33
|
+
Contributions are welcome! To contribute:
|
|
34
|
+
|
|
35
|
+
1. Fork the repository.
|
|
36
|
+
2. Create a feature branch (`git checkout -b feature/YourFeature`).
|
|
37
|
+
3. Commit your changes (`git commit -m "Add your message"`).
|
|
38
|
+
4. Push to the branch (`git push origin feature/YourFeature`).
|
|
39
|
+
5. Open a Pull Request.
|
|
40
|
+
|
|
41
|
+
Please include tests (if applicable) and follow PEP 8 style guidelines.
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
This project is licensed under the **MIT License** - see the LICENSE file for details.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "scietex.hal.qcm"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Scietex Quatz Microbalance HAL package"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Anton Bondarenko", email = "bond.anton@gmail.com" },
|
|
11
|
+
]
|
|
12
|
+
license = { file = "LICENSE" }
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
requires-python = ">=3.9"
|
|
20
|
+
dependencies = [
|
|
21
|
+
"msgspec[yaml] ~= 0.19.0",
|
|
22
|
+
"scietex.hal.serial >= 1.0.5, < 2.0.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
"Homepage" = "https://github.com/bond-anton/scietex.hal.qcm"
|
|
27
|
+
"Bug Tracker" = "https://github.com/bond-anton/scietex.hal.qcm/issues"
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = []
|
|
31
|
+
test = ["flake8", "pytest", "pytest-asyncio"]
|
|
32
|
+
lint = ["pylint"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.dynamic]
|
|
35
|
+
version = {attr = "scietex.hal.qcm.version.__version__"}
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.package-data]
|
|
38
|
+
"scietex.hal.qcm" = ["py.typed"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.black]
|
|
44
|
+
line-length = 100
|
|
45
|
+
|
|
46
|
+
[tool.mypy]
|
|
47
|
+
python_version = "3.9"
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
pythonpath = [
|
|
51
|
+
"src"
|
|
52
|
+
]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Data models for the QCM."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
import msgspec
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OutCTRLMode(Enum):
|
|
8
|
+
"""Modes of the control output."""
|
|
9
|
+
|
|
10
|
+
HIGH = 1
|
|
11
|
+
LOW = 2
|
|
12
|
+
MANUAL = 3
|
|
13
|
+
DISABLED = 4
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PwmCTRLMode(Enum):
|
|
17
|
+
"""Modes of the PWM output."""
|
|
18
|
+
|
|
19
|
+
ABSOLUTE = 1
|
|
20
|
+
RELATIVE = 2
|
|
21
|
+
MANUAL = 3
|
|
22
|
+
DISABLED = 4
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# pylint: disable=too-few-public-methods
|
|
26
|
+
class QCMError(msgspec.Struct, frozen=True):
|
|
27
|
+
"""Model of a QCM error with code and message."""
|
|
28
|
+
|
|
29
|
+
code: int
|
|
30
|
+
message: str = "Unknown error"
|
|
31
|
+
|
|
32
|
+
def __str__(self) -> str:
|
|
33
|
+
return f"QCMError(code={self.code}, message='{self.message}')"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# pylint: disable=too-few-public-methods
|
|
37
|
+
class FTMParameters(msgspec.Struct, frozen=True):
|
|
38
|
+
"""Model for FTM operational parameters."""
|
|
39
|
+
|
|
40
|
+
frequency: float = 0.0 # Sensor frequency
|
|
41
|
+
frequency_std: float = 0.0 # Sensor frequency standard deviation
|
|
42
|
+
averaging_window: int = 1 # Averaging window size
|
|
43
|
+
averaging_progress: int = 1 # Averaging accumulation progress
|
|
44
|
+
rate: float = 0.0 # Deposition rate A/s
|
|
45
|
+
thickness: float = 0.0 # Film thickness A
|
|
46
|
+
thickness_std: float = 0.0 # Film thickness standard deviation A
|
|
47
|
+
target: float = 0.0 # Film target thickness A
|
|
48
|
+
material_density: float = 0.0 # Material density g/cm^3
|
|
49
|
+
material_z_ratio: float = 0.0 # Material Z-ratio
|
|
50
|
+
running: bool = False # Current FTM measurement running state
|
|
51
|
+
scale: float = 1.0 # Scale (tooling) factor
|
|
52
|
+
|
|
53
|
+
def __str__(self) -> str:
|
|
54
|
+
return (
|
|
55
|
+
f"FTMParameters(frequency={self.frequency:g} +- {self.frequency_std:g} Hz, "
|
|
56
|
+
f"averaging={self.averaging_progress}/{self.averaging_window}, "
|
|
57
|
+
f"deposition rate={self.rate} A/s, "
|
|
58
|
+
f"film thickness={self.thickness} +- {self.thickness_std} A, "
|
|
59
|
+
f"target thickness={self.target} A, "
|
|
60
|
+
f"material density={self.material_density} g/cm^3, "
|
|
61
|
+
f"material Z-ratio={self.material_z_ratio}, "
|
|
62
|
+
f"running={self.running})"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# pylint: disable=too-few-public-methods
|
|
67
|
+
class FTMStartCMD(msgspec.Struct, frozen=True):
|
|
68
|
+
"""FTM start command with parameters."""
|
|
69
|
+
|
|
70
|
+
reset: bool = True # Reset thickness at start
|
|
71
|
+
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
return f"FTMStartCMD(reset={self.reset})"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# pylint: disable=too-few-public-methods
|
|
77
|
+
class Material(msgspec.Struct, frozen=True):
|
|
78
|
+
"""Material parameters."""
|
|
79
|
+
|
|
80
|
+
density: float # Density g/cm^3
|
|
81
|
+
z_ratio: float # Z-ratio
|
|
82
|
+
|
|
83
|
+
def __str__(self) -> str:
|
|
84
|
+
return f"Material(density={self.density}, Z-ratio={self.z_ratio})"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Functions for film thickness calculation"""
|
|
2
|
+
|
|
3
|
+
import math as m
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
NQ = 1.668e5 # Hz*cm Frequency const of AT-cut quartz
|
|
7
|
+
RQ = 2.648 # g/cm^3 Density of quartz
|
|
8
|
+
MQ = 2.947e11 # g/(cm*s^2) Shear modulus of quartz
|
|
9
|
+
|
|
10
|
+
NRQ = 140593.14771292824 # NQ * RQ / m.pi [Hz * g / cm^2]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def freq_change_to_mass_per_cm2(f0, f1, z):
|
|
14
|
+
"""Calculate film mass per cm2 (g / cm^2)"""
|
|
15
|
+
return NRQ / (z * f1) * m.atan(z * m.tan(m.pi * (f0 - f1) / f0)) # g / cm^2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def freq_change_to_thickness(f0, f1, rho, z):
|
|
19
|
+
"""Calculate film thickness (A)"""
|
|
20
|
+
m_per_cm2 = freq_change_to_mass_per_cm2(f0, f1, z)
|
|
21
|
+
return m_per_cm2 / rho * 1e8 # Angstrom
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reference dict for different materials.
|
|
3
|
+
Density is given in g/cm^3.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
materials = {
|
|
7
|
+
"Ag": {
|
|
8
|
+
"density": 10.5,
|
|
9
|
+
"z_ratio": 0.53,
|
|
10
|
+
},
|
|
11
|
+
"Al": {
|
|
12
|
+
"density": 2.7,
|
|
13
|
+
"z_ratio": 1.08,
|
|
14
|
+
},
|
|
15
|
+
"Au": {
|
|
16
|
+
"density": 19.3,
|
|
17
|
+
"z_ratio": 0.38,
|
|
18
|
+
},
|
|
19
|
+
"C": {
|
|
20
|
+
"density": 2.25,
|
|
21
|
+
"z_ratio": 3.26,
|
|
22
|
+
},
|
|
23
|
+
"Cr": {
|
|
24
|
+
"density": 7.2,
|
|
25
|
+
"z_ratio": 0.31,
|
|
26
|
+
},
|
|
27
|
+
"Cu": {
|
|
28
|
+
"density": 8.93,
|
|
29
|
+
"z_ratio": 0.44,
|
|
30
|
+
},
|
|
31
|
+
"Mo": {
|
|
32
|
+
"density": 10.2,
|
|
33
|
+
"z_ratio": 0.26,
|
|
34
|
+
},
|
|
35
|
+
"Ni": {
|
|
36
|
+
"density": 8.91,
|
|
37
|
+
"z_ratio": 0.33,
|
|
38
|
+
},
|
|
39
|
+
"Pd": {
|
|
40
|
+
"density": 12.04,
|
|
41
|
+
"z_ratio": 0.36,
|
|
42
|
+
},
|
|
43
|
+
"Pt": {
|
|
44
|
+
"density": 21.4,
|
|
45
|
+
"z_ratio": 0.25,
|
|
46
|
+
},
|
|
47
|
+
"Ti": {
|
|
48
|
+
"density": 4.5,
|
|
49
|
+
"z_ratio": 0.63,
|
|
50
|
+
},
|
|
51
|
+
"V": {
|
|
52
|
+
"density": 5.96,
|
|
53
|
+
"z_ratio": 0.53,
|
|
54
|
+
},
|
|
55
|
+
"W": {
|
|
56
|
+
"density": 19.3,
|
|
57
|
+
"z_ratio": 0.16,
|
|
58
|
+
},
|
|
59
|
+
"Zn": {
|
|
60
|
+
"density": 7.04,
|
|
61
|
+
"z_ratio": 0.51,
|
|
62
|
+
},
|
|
63
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Gated FTM abstract interface."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from .data import QCMError, PwmCTRLMode, OutCTRLMode, FTMParameters, Material
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# pylint: disable=too-many-public-methods
|
|
9
|
+
class GatedFTM(ABC):
|
|
10
|
+
"""Gated Film Thickness Monitor interface class."""
|
|
11
|
+
|
|
12
|
+
error_codes: dict[int, QCMError] = {0: QCMError(code=0, message="No error")}
|
|
13
|
+
error_codes_com: dict[int, QCMError] = {0: QCMError(code=0, message="No error")}
|
|
14
|
+
|
|
15
|
+
# Error processing methods
|
|
16
|
+
@abstractmethod
|
|
17
|
+
async def get_error_code(self, comm: bool = False) -> int:
|
|
18
|
+
"""Read error code from the FTM"""
|
|
19
|
+
|
|
20
|
+
def parse_error_code(self, code: int, comm: bool = False) -> QCMError:
|
|
21
|
+
"""Parse error code from the FTM"""
|
|
22
|
+
if comm:
|
|
23
|
+
if code in self.error_codes_com:
|
|
24
|
+
return self.error_codes_com[code]
|
|
25
|
+
else:
|
|
26
|
+
if code in self.error_codes:
|
|
27
|
+
return self.error_codes[code]
|
|
28
|
+
return QCMError(code=code, message="Unknown error")
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
async def clear_error(self, comm: bool = False) -> int:
|
|
32
|
+
"""Clear error from the FTM"""
|
|
33
|
+
|
|
34
|
+
# Device information methods
|
|
35
|
+
@abstractmethod
|
|
36
|
+
async def get_vendor(self) -> str:
|
|
37
|
+
"""Get QCM vendor name."""
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
async def get_product_name(self) -> str:
|
|
41
|
+
"""Get QCM product name."""
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
async def get_serial_number(self) -> str:
|
|
45
|
+
"""Get QCM serial number."""
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
async def get_version(self) -> str:
|
|
49
|
+
"""get QCM firmware version"""
|
|
50
|
+
|
|
51
|
+
# FTM measurement setup
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
async def get_mcu_frequency(self) -> int:
|
|
55
|
+
"""GET MCU frequency."""
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
async def get_gate_time(self) -> float:
|
|
59
|
+
"""Get gate time in ms"""
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
async def set_gate_time(self, gate_time_ms: float) -> float:
|
|
63
|
+
"""Set gate time in ms"""
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
async def get_averaging(self) -> int:
|
|
67
|
+
"""Get averaging window"""
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
async def set_averaging(self, averaging_window: int) -> int:
|
|
71
|
+
"""Set averaging window."""
|
|
72
|
+
|
|
73
|
+
# FTM frequency measurement data access methods
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
async def get_counter(self) -> int:
|
|
77
|
+
"""Get FTM raw counts."""
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
async def get_frequency_instant(self) -> float:
|
|
81
|
+
"""Get instant frequency."""
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
async def get_averaging_progress(self) -> int:
|
|
85
|
+
"""Get averaging accumulation progress."""
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
async def get_frequency(self) -> float:
|
|
89
|
+
"""Get frequency."""
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
async def get_frequency_std(self) -> float:
|
|
93
|
+
"""Get frequency standard deviation."""
|
|
94
|
+
|
|
95
|
+
# Thickness measurement methods
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
async def get_thickness(self) -> float:
|
|
99
|
+
"""Get thickness (Angstrom) value."""
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
async def get_thickness_std(self) -> float:
|
|
103
|
+
"""Parse thickness (Angstrom) standard deviation."""
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
async def get_rate(self) -> float:
|
|
107
|
+
"""Parse rate (Angstrom/s) value from register data"""
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
async def get_material_density(self) -> float:
|
|
111
|
+
"""Get material density g/cm3"""
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
async def set_material_density(self, density: float) -> float:
|
|
115
|
+
"""Set material density g/cm3."""
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
async def get_material_z_ratio(self) -> float:
|
|
119
|
+
"""Get material Z-ratio."""
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
async def set_material_z_ratio(self, z_ratio: float) -> float:
|
|
123
|
+
"""Set material Z-ratio."""
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
async def get_material(self) -> Material:
|
|
127
|
+
"""Get deposition material density and Z-ratio"""
|
|
128
|
+
|
|
129
|
+
@abstractmethod
|
|
130
|
+
async def set_material(self, material: Material) -> Material:
|
|
131
|
+
"""Set deposition material density and Z-ratio"""
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
async def get_ftm_scale(self) -> float:
|
|
135
|
+
"""Get FTM scale factor."""
|
|
136
|
+
|
|
137
|
+
@abstractmethod
|
|
138
|
+
async def set_ftm_scale(self, scale: float) -> float:
|
|
139
|
+
"""Set FTM scale factor."""
|
|
140
|
+
|
|
141
|
+
@abstractmethod
|
|
142
|
+
async def get_target_thickness(self) -> float:
|
|
143
|
+
"""Get target thickness (Angstrom)."""
|
|
144
|
+
|
|
145
|
+
@abstractmethod
|
|
146
|
+
async def set_target_thickness(self, target: float) -> float:
|
|
147
|
+
"""Set target thickness (Angstrom)."""
|
|
148
|
+
|
|
149
|
+
async def get_target_nm(self) -> float:
|
|
150
|
+
"""Get target thickness (nm)."""
|
|
151
|
+
return await self.get_target_thickness() / 10
|
|
152
|
+
|
|
153
|
+
async def set_target_nm(self, target: float) -> float:
|
|
154
|
+
"""Set target thickness (nm)."""
|
|
155
|
+
return await self.set_target_thickness(target * 10)
|
|
156
|
+
|
|
157
|
+
async def get_target_um(self) -> float:
|
|
158
|
+
"""Get target thickness (nm)."""
|
|
159
|
+
return await self.get_target_thickness() / 10_000
|
|
160
|
+
|
|
161
|
+
async def set_target_um(self, target: float) -> float:
|
|
162
|
+
"""Set target thickness (um)."""
|
|
163
|
+
return await self.set_target_thickness(target * 10_000)
|
|
164
|
+
|
|
165
|
+
@abstractmethod
|
|
166
|
+
async def reset_thickness(self) -> None:
|
|
167
|
+
"""Reset measured thickness."""
|
|
168
|
+
|
|
169
|
+
# CTRL OUT parameters
|
|
170
|
+
|
|
171
|
+
@abstractmethod
|
|
172
|
+
async def get_ctrl_out_mode(self) -> OutCTRLMode:
|
|
173
|
+
"""Get the mode of the control output."""
|
|
174
|
+
|
|
175
|
+
@abstractmethod
|
|
176
|
+
async def set_ctrl_out_mode(self, mode: OutCTRLMode) -> OutCTRLMode:
|
|
177
|
+
"""Set the mode of the control output."""
|
|
178
|
+
|
|
179
|
+
@abstractmethod
|
|
180
|
+
async def get_ctrl_out_value(self) -> bool:
|
|
181
|
+
"""Get control output value."""
|
|
182
|
+
|
|
183
|
+
@abstractmethod
|
|
184
|
+
async def set_ctrl_out_value(self, value: bool) -> bool:
|
|
185
|
+
"""Set control output value."""
|
|
186
|
+
|
|
187
|
+
@abstractmethod
|
|
188
|
+
async def get_ctrl_pwm_mode(self) -> PwmCTRLMode:
|
|
189
|
+
"""Get the mode of the PWM output."""
|
|
190
|
+
|
|
191
|
+
@abstractmethod
|
|
192
|
+
async def set_ctrl_pwm_mode(self, mode: PwmCTRLMode) -> PwmCTRLMode:
|
|
193
|
+
"""Set the mode of the PWM output."""
|
|
194
|
+
|
|
195
|
+
@abstractmethod
|
|
196
|
+
async def get_ctrl_pwm_value(self) -> float:
|
|
197
|
+
"""Get PWM output value."""
|
|
198
|
+
|
|
199
|
+
@abstractmethod
|
|
200
|
+
async def set_ctrl_pwm_value(self, value: float) -> float:
|
|
201
|
+
"""Set PWM output value."""
|
|
202
|
+
|
|
203
|
+
@abstractmethod
|
|
204
|
+
async def get_ctrl_pwm_scale(self) -> int:
|
|
205
|
+
"""Get PWM output scale."""
|
|
206
|
+
|
|
207
|
+
@abstractmethod
|
|
208
|
+
async def set_ctrl_pwm_scale(self, scale: int) -> int:
|
|
209
|
+
"""Set PWM output scale."""
|
|
210
|
+
|
|
211
|
+
# MEASUREMENT START/STOP
|
|
212
|
+
|
|
213
|
+
@abstractmethod
|
|
214
|
+
async def get_running_state(self) -> bool:
|
|
215
|
+
"""Get running state."""
|
|
216
|
+
|
|
217
|
+
@abstractmethod
|
|
218
|
+
async def set_running_state(self, running: bool) -> bool:
|
|
219
|
+
"""Set running state."""
|
|
220
|
+
|
|
221
|
+
async def start_measurement(self, reset: bool = True) -> bool:
|
|
222
|
+
"""Start measurement"""
|
|
223
|
+
if reset:
|
|
224
|
+
await self.reset_thickness()
|
|
225
|
+
return await self.set_running_state(True)
|
|
226
|
+
|
|
227
|
+
async def stop_measurement(self) -> bool:
|
|
228
|
+
"""Stop measurement."""
|
|
229
|
+
return await self.set_running_state(False)
|
|
230
|
+
|
|
231
|
+
# Read FTM parameters in single call
|
|
232
|
+
|
|
233
|
+
@abstractmethod
|
|
234
|
+
async def read_parameters(self) -> FTMParameters:
|
|
235
|
+
"""Read FTM Parameters"""
|