labbench-comm 0.1.1__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.
Files changed (56) hide show
  1. labbench_comm-0.1.1/LICENSE +21 -0
  2. labbench_comm-0.1.1/PKG-INFO +97 -0
  3. labbench_comm-0.1.1/README.md +46 -0
  4. labbench_comm-0.1.1/pyproject.toml +65 -0
  5. labbench_comm-0.1.1/setup.cfg +4 -0
  6. labbench_comm-0.1.1/src/labbench_comm/__init__.py +3 -0
  7. labbench_comm-0.1.1/src/labbench_comm/_version.py +1 -0
  8. labbench_comm-0.1.1/src/labbench_comm/cli.py +18 -0
  9. labbench_comm-0.1.1/src/labbench_comm/devices/__init__.py +0 -0
  10. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/__init__.py +92 -0
  11. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/codec.py +50 -0
  12. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/cpar_central.py +117 -0
  13. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/definitions.py +88 -0
  14. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/functions/__init__.py +13 -0
  15. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/functions/clear_waveform_programs.py +20 -0
  16. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/functions/set_operating_mode.py +42 -0
  17. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/functions/set_waveform_program.py +142 -0
  18. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/functions/start_stimulation.py +84 -0
  19. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/functions/stop_stimulation.py +28 -0
  20. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/instruction_codec.py +151 -0
  21. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/messages/__init__.py +7 -0
  22. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/messages/event_message.py +44 -0
  23. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/messages/status_message.py +263 -0
  24. labbench_comm-0.1.1/src/labbench_comm/devices/cpar/waveform.py +84 -0
  25. labbench_comm-0.1.1/src/labbench_comm/protocols/__init__.py +96 -0
  26. labbench_comm-0.1.1/src/labbench_comm/protocols/bus_central.py +195 -0
  27. labbench_comm-0.1.1/src/labbench_comm/protocols/destuffer.py +130 -0
  28. labbench_comm-0.1.1/src/labbench_comm/protocols/device.py +185 -0
  29. labbench_comm-0.1.1/src/labbench_comm/protocols/device_function.py +206 -0
  30. labbench_comm-0.1.1/src/labbench_comm/protocols/device_message.py +108 -0
  31. labbench_comm-0.1.1/src/labbench_comm/protocols/error_codes.py +12 -0
  32. labbench_comm-0.1.1/src/labbench_comm/protocols/exceptions.py +88 -0
  33. labbench_comm-0.1.1/src/labbench_comm/protocols/frame.py +44 -0
  34. labbench_comm-0.1.1/src/labbench_comm/protocols/function_dispatcher.py +35 -0
  35. labbench_comm-0.1.1/src/labbench_comm/protocols/functions/__init__.py +22 -0
  36. labbench_comm-0.1.1/src/labbench_comm/protocols/functions/device_identification.py +125 -0
  37. labbench_comm-0.1.1/src/labbench_comm/protocols/functions/ping.py +40 -0
  38. labbench_comm-0.1.1/src/labbench_comm/protocols/manufacturer.py +35 -0
  39. labbench_comm-0.1.1/src/labbench_comm/protocols/message_dispatcher.py +37 -0
  40. labbench_comm-0.1.1/src/labbench_comm/protocols/messages/__init__.py +20 -0
  41. labbench_comm-0.1.1/src/labbench_comm/protocols/messages/printf_message.py +57 -0
  42. labbench_comm-0.1.1/src/labbench_comm/protocols/packet.py +270 -0
  43. labbench_comm-0.1.1/src/labbench_comm/serial/__init__.py +0 -0
  44. labbench_comm-0.1.1/src/labbench_comm/serial/async_connection.py +29 -0
  45. labbench_comm-0.1.1/src/labbench_comm/serial/async_serial_connection.py +124 -0
  46. labbench_comm-0.1.1/src/labbench_comm/serial/base.py +46 -0
  47. labbench_comm-0.1.1/src/labbench_comm/serial/connection.py +164 -0
  48. labbench_comm-0.1.1/src/labbench_comm/utils/__init__.py +0 -0
  49. labbench_comm-0.1.1/src/labbench_comm/utils/additive_checksum.py +22 -0
  50. labbench_comm-0.1.1/src/labbench_comm/utils/crc8_ccitt.py +38 -0
  51. labbench_comm-0.1.1/src/labbench_comm.egg-info/PKG-INFO +97 -0
  52. labbench_comm-0.1.1/src/labbench_comm.egg-info/SOURCES.txt +54 -0
  53. labbench_comm-0.1.1/src/labbench_comm.egg-info/dependency_links.txt +1 -0
  54. labbench_comm-0.1.1/src/labbench_comm.egg-info/entry_points.txt +2 -0
  55. labbench_comm-0.1.1/src/labbench_comm.egg-info/requires.txt +7 -0
  56. labbench_comm-0.1.1/src/labbench_comm.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LabBench Society
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,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: labbench_comm
3
+ Version: 0.1.1
4
+ Summary: Communication with LabBench devices
5
+ Author: Kristian Hennings
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 LabBench Society
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
+
28
+ Project-URL: Homepage, https://labbench.io/
29
+ Project-URL: Repository, https://github.com/LabBench-Society/LabBench.Python
30
+ Project-URL: Issues, https://github.com/LabBench-Society/LabBench.Python/issues
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Science/Research
33
+ Classifier: Topic :: Scientific/Engineering
34
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.9
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Requires-Python: >=3.12
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: pyserial>=3.5
45
+ Requires-Dist: typing-extensions>=4.5
46
+ Provides-Extra: dev
47
+ Requires-Dist: pytest>=8.0; extra == "dev"
48
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
49
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
50
+ Dynamic: license-file
51
+
52
+ # labbench-comm
53
+
54
+ **labbench-comm** is an asynchronous Python framework for communicating with LabBench hardware devices over serial connections.
55
+ It provides a robust, testable, and extensible protocol stack with concrete device implementations, starting with the **CPAR+** device.
56
+
57
+ The package is designed for:
58
+ - Scientific and clinical research setups
59
+ - Hardware control and automation
60
+ - Deterministic, protocol-driven device communication
61
+ - Async-first Python applications (`asyncio`)
62
+
63
+ ---
64
+
65
+ ## Features
66
+
67
+ - **Async-first architecture** using `asyncio`
68
+ - **Robust serial communication** built on `pyserial`
69
+ - **Protocol abstraction** (framing, packets, checksums, dispatch)
70
+ - **Typed device functions and messages**
71
+ - **Extensible device model** for adding new LabBench devices
72
+ - **Unit tests + hardware integration tests**
73
+ - **CPAR+ device support** (functions, messages, waveform control)
74
+
75
+ ---
76
+
77
+ ## Supported Devices
78
+
79
+ - **CPAR+** (pressure stimulation device)
80
+
81
+ Additional devices can be added by implementing new `Device`, `DeviceFunction`, and `DeviceMessage` classes.
82
+
83
+ ---
84
+
85
+ ## Requirements
86
+
87
+ - Python **3.12+**
88
+ - Supported platforms: Windows, Linux, macOS (serial access required)
89
+
90
+ ---
91
+
92
+ ## Installation
93
+
94
+ Install from PyPI:
95
+
96
+ ```bash
97
+ pip install labbench-comm
@@ -0,0 +1,46 @@
1
+ # labbench-comm
2
+
3
+ **labbench-comm** is an asynchronous Python framework for communicating with LabBench hardware devices over serial connections.
4
+ It provides a robust, testable, and extensible protocol stack with concrete device implementations, starting with the **CPAR+** device.
5
+
6
+ The package is designed for:
7
+ - Scientific and clinical research setups
8
+ - Hardware control and automation
9
+ - Deterministic, protocol-driven device communication
10
+ - Async-first Python applications (`asyncio`)
11
+
12
+ ---
13
+
14
+ ## Features
15
+
16
+ - **Async-first architecture** using `asyncio`
17
+ - **Robust serial communication** built on `pyserial`
18
+ - **Protocol abstraction** (framing, packets, checksums, dispatch)
19
+ - **Typed device functions and messages**
20
+ - **Extensible device model** for adding new LabBench devices
21
+ - **Unit tests + hardware integration tests**
22
+ - **CPAR+ device support** (functions, messages, waveform control)
23
+
24
+ ---
25
+
26
+ ## Supported Devices
27
+
28
+ - **CPAR+** (pressure stimulation device)
29
+
30
+ Additional devices can be added by implementing new `Device`, `DeviceFunction`, and `DeviceMessage` classes.
31
+
32
+ ---
33
+
34
+ ## Requirements
35
+
36
+ - Python **3.12+**
37
+ - Supported platforms: Windows, Linux, macOS (serial access required)
38
+
39
+ ---
40
+
41
+ ## Installation
42
+
43
+ Install from PyPI:
44
+
45
+ ```bash
46
+ pip install labbench-comm
@@ -0,0 +1,65 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "labbench_comm"
7
+ version = "0.1.1"
8
+ description = "Communication with LabBench devices"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [
12
+ { name = "Kristian Hennings" }
13
+ ]
14
+ requires-python = ">=3.12"
15
+
16
+ dependencies = [
17
+ "pyserial>=3.5",
18
+ "typing-extensions>=4.5",
19
+ ]
20
+
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Intended Audience :: Science/Research",
24
+ "Topic :: Scientific/Engineering",
25
+ "Topic :: System :: Hardware :: Hardware Drivers",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.9",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ ]
33
+
34
+ [project.scripts]
35
+ labbench-comm = "labbench_comm.cli:main"
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=8.0",
40
+ "pytest-asyncio>=0.23",
41
+ "pytest-cov>=5.0"
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://labbench.io/"
46
+ Repository = "https://github.com/LabBench-Society/LabBench.Python"
47
+ Issues = "https://github.com/LabBench-Society/LabBench.Python/issues"
48
+
49
+ [tool.setuptools]
50
+ package-dir = { "" = "src" }
51
+
52
+ [tool.setuptools.packages.find]
53
+ where = ["src"]
54
+
55
+ [tool.pytest.ini_options]
56
+ testpaths = ["tests"]
57
+ addopts = "-ra"
58
+ markers = [
59
+ "cpar: integration tests for the LabBench CPAR+ device",
60
+ "hardware: tests that require real hardware",
61
+ "unittest: unit tests of individual classes"
62
+ ]
63
+ log_cli = true
64
+ log_cli_level = "DEBUG"
65
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from ._version import __version__
2
+
3
+ __all__ = ["__version__"]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.1"
@@ -0,0 +1,18 @@
1
+ import argparse
2
+
3
+ from labbench_comm import __version__
4
+
5
+
6
+ def main() -> None:
7
+ parser = argparse.ArgumentParser(
8
+ prog="labbench-comm",
9
+ description="LabBench device communication toolkit",
10
+ )
11
+
12
+ parser.add_argument(
13
+ "--version",
14
+ action="version",
15
+ version=f"%(prog)s {__version__}",
16
+ )
17
+
18
+ parser.parse_args()
@@ -0,0 +1,92 @@
1
+ """
2
+ CPAR+ device support.
3
+
4
+ This package provides:
5
+ - The CPARplusCentral device implementation
6
+ - CPAR-specific functions and messages
7
+ - Waveform and instruction utilities
8
+ - Device-specific enums and definitions
9
+ """
10
+
11
+ # ----------------------------------------------------------------------
12
+ # Device
13
+ # ----------------------------------------------------------------------
14
+
15
+ from .cpar_central import CPARplusCentral
16
+
17
+ # ----------------------------------------------------------------------
18
+ # Definitions / enums
19
+ # ----------------------------------------------------------------------
20
+
21
+ from .definitions import (
22
+ DeviceChannelID,
23
+ DeviceState,
24
+ EcpError,
25
+ EventID,
26
+ OperatingMode,
27
+ StopCondition,
28
+ StopCriterion,
29
+ WaveformInstructionType,
30
+ )
31
+
32
+ # ----------------------------------------------------------------------
33
+ # Waveform
34
+ # ----------------------------------------------------------------------
35
+
36
+ from .waveform import WaveformInstruction
37
+ from .instruction_codec import InstructionCodec
38
+
39
+ # ----------------------------------------------------------------------
40
+ # Functions
41
+ # ----------------------------------------------------------------------
42
+
43
+ from .functions import (
44
+ SetWaveformProgram,
45
+ StartStimulation,
46
+ StopStimulation,
47
+ SetOperatingMode,
48
+ ClearWaveformPrograms,
49
+ )
50
+
51
+ # ----------------------------------------------------------------------
52
+ # Messages
53
+ # ----------------------------------------------------------------------
54
+
55
+ from .messages import (
56
+ StatusMessage,
57
+ EventMessage,
58
+ )
59
+
60
+ # ----------------------------------------------------------------------
61
+ # Public API
62
+ # ----------------------------------------------------------------------
63
+
64
+ __all__ = [
65
+ # Device
66
+ "CPARplusCentral",
67
+
68
+ # Enums / definitions
69
+ "DeviceChannelID",
70
+ "DeviceState",
71
+ "EcpError",
72
+ "EventID",
73
+ "OperatingMode",
74
+ "StopCondition",
75
+ "StopCriterion",
76
+ "WaveformInstructionType",
77
+
78
+ # Waveform
79
+ "WaveformInstruction",
80
+ "InstructionCodec",
81
+
82
+ # Functions
83
+ "SetWaveformProgram",
84
+ "StartStimulation",
85
+ "StopStimulation",
86
+ "SetOperatingMode",
87
+ "ClearWaveformPrograms",
88
+
89
+ # Messages
90
+ "StatusMessage",
91
+ "EventMessage",
92
+ ]
@@ -0,0 +1,50 @@
1
+ from enum import Enum
2
+ from labbench_comm.devices.cpar.instruction_codec import InstructionCodec
3
+
4
+
5
+ class CPARplusCodec:
6
+ class PressureType(Enum):
7
+ SUPPLY_PRESSURE = 0
8
+ STIMULATING_PRESSURE = 1
9
+
10
+ MAX_SUPPLY_PRESSURE = 1000.0
11
+ MAX_SCORE = 10.0
12
+
13
+ @staticmethod
14
+ def time_to_rate(time: float) -> int:
15
+ return round(time * InstructionCodec.UPDATE_RATE)
16
+
17
+ @staticmethod
18
+ def binary_to_pressure(
19
+ value: int,
20
+ pressure_type: "CPARplusCodec.PressureType" = PressureType.STIMULATING_PRESSURE,
21
+ ) -> float:
22
+ if pressure_type is CPARplusCodec.PressureType.SUPPLY_PRESSURE:
23
+ return CPARplusCodec.MAX_SUPPLY_PRESSURE * value / 4095
24
+ return InstructionCodec.MAX_PRESSURE * value / 4095
25
+
26
+ @staticmethod
27
+ def binary_to_score(value: int) -> float:
28
+ return CPARplusCodec.MAX_SCORE * value / 255
29
+
30
+ @staticmethod
31
+ def pressure_to_binary(pressure: float) -> int:
32
+ return InstructionCodec._pressure_to_binary(pressure)
33
+
34
+ @staticmethod
35
+ def delta_pressure_to_binary(delta: float) -> int:
36
+ return InstructionCodec._pressure_to_binary(
37
+ delta / InstructionCodec.UPDATE_RATE
38
+ )
39
+
40
+ @staticmethod
41
+ def count_to_time(count: int) -> float:
42
+ return count / InstructionCodec.UPDATE_RATE
43
+
44
+ @staticmethod
45
+ def time_to_count(time: float) -> int:
46
+ return int((time * InstructionCodec.UPDATE_RATE) + 0.9999)
47
+
48
+ @staticmethod
49
+ def get_time(samples: int) -> float:
50
+ return CPARplusCodec.count_to_time(samples)
@@ -0,0 +1,117 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import Optional
5
+
6
+ from labbench_comm.protocols.device import Device
7
+ from labbench_comm.protocols.device_function import DeviceFunction
8
+ from labbench_comm.protocols.functions.device_identification import DeviceIdentification
9
+ from labbench_comm.protocols.functions.ping import Ping
10
+ from labbench_comm.devices.cpar.messages import (
11
+ StatusMessage,
12
+ EventMessage,
13
+ )
14
+ from labbench_comm.devices.cpar.definitions import (
15
+ DeviceState,
16
+ EcpError,
17
+ )
18
+ from labbench_comm.devices.cpar.instruction_codec import InstructionCodec
19
+ from labbench_comm.protocols.manufacturer import Manufacturer
20
+
21
+
22
+ class CPARplusCentral(Device):
23
+ # ------------------------------------------------------------------
24
+ # Construction
25
+ # ------------------------------------------------------------------
26
+
27
+ def __init__(self, bus) -> None:
28
+ super().__init__(bus)
29
+
30
+ self.baudrate = 38400
31
+ self.retries = 3
32
+
33
+ # --- CPAR+ messages ---
34
+ self.add_message(EventMessage())
35
+ self.add_message(StatusMessage())
36
+
37
+ # --- Runtime state ---
38
+ self.state: Optional[DeviceState] = None
39
+
40
+ self.actual_pressure_01 = 0.0
41
+ self.target_pressure_01 = 0.0
42
+ self.final_pressure_01 = 0.0
43
+
44
+ self.actual_pressure_02 = 0.0
45
+ self.target_pressure_02 = 0.0
46
+ self.final_pressure_02 = 0.0
47
+
48
+ self.response_connected = False
49
+ self.vas_is_low = False
50
+ self.vas_score = 0.0
51
+ self.final_vas_score = 0.0
52
+
53
+ self.status_received = []
54
+ self.event_received = []
55
+
56
+ # ------------------------------------------------------------------
57
+ # Error handling
58
+ # ------------------------------------------------------------------
59
+
60
+ def get_peripheral_error_string(self, error_code: int) -> str:
61
+ try:
62
+ return str(EcpError(error_code))
63
+ except ValueError:
64
+ return f"Unknown CPAR error ({error_code})"
65
+
66
+ # ------------------------------------------------------------------
67
+ # Message handlers
68
+ # ------------------------------------------------------------------
69
+
70
+ def on_status_message(self, message: StatusMessage) -> None:
71
+ if message is None:
72
+ return
73
+
74
+ self.actual_pressure_01 = message.actual_pressure_01
75
+ self.target_pressure_01 = message.target_pressure_01
76
+ self.final_pressure_01 = message.final_pressure_01
77
+
78
+ self.actual_pressure_02 = message.actual_pressure_02
79
+ self.target_pressure_02 = message.target_pressure_02
80
+ self.final_pressure_02 = message.final_pressure_02
81
+
82
+ self.response_connected = message.vas_connected
83
+ self.vas_is_low = message.vas_is_low
84
+ self.vas_score = message.vas_score
85
+ self.final_vas_score = message.final_vas_score
86
+
87
+ self.state = message.system_state
88
+
89
+ for cb in self.status_received:
90
+ cb(self, message)
91
+
92
+ def on_event_message(self, message: EventMessage) -> None:
93
+ if message is None:
94
+ return
95
+
96
+ for cb in self.event_received:
97
+ cb(self, message)
98
+
99
+ # ------------------------------------------------------------------
100
+ # Compatibility
101
+ # ------------------------------------------------------------------
102
+
103
+ def is_compatible(self, function: DeviceFunction) -> bool:
104
+ if not isinstance(function, DeviceIdentification):
105
+ return False
106
+
107
+ return (
108
+ function.manufacturer_id == Manufacturer.InventorsWay
109
+ and function.device_id == 4
110
+ )
111
+
112
+ # ------------------------------------------------------------------
113
+ # Representation
114
+ # ------------------------------------------------------------------
115
+
116
+ def __str__(self) -> str:
117
+ return "CPAR+ Device"
@@ -0,0 +1,88 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class DeviceChannelID(IntEnum):
5
+ CH01 = 0
6
+ CH02 = 1
7
+ NONE = 2
8
+
9
+ class DeviceState(IntEnum):
10
+ STATE_NOT_CONNECTED = 0
11
+ STATE_IDLE = 1
12
+ STATE_STIMULATING = 2
13
+ STATE_EMERGENCY = 3
14
+ STATE_PENDING = 4
15
+
16
+ class EcpError(IntEnum):
17
+ NO_ERROR = 0
18
+ UNKNOWN_FUNCTION_ERR = 1
19
+ INVALID_REQUEST_LENGTH_ERR = 2
20
+ SYSTEM_NOT_IDLE_ERR = 3
21
+ INCORRECT_CHANNEL_ERR = 4
22
+ NO_REPONSE_DEVICE_CONNECTED_ERR = 5
23
+ RATING_IS_NOT_ZERO_ERR = 6
24
+ NO_SUPPLY_PRESSURE_ERR = 7
25
+ NO_POWER_ERR = 8
26
+ INVALID_START_CONFIGURATION = 9
27
+ STOP_NOT_POSSIBLE_ERR = 10
28
+ INVALID_EVENT_ADDRESS_ERR = 11
29
+ MAX_WAVEFORM_DURATION_EXCEEDED_ERR = 12
30
+ INVALID_ADDRESS_ERR = 13
31
+ COULD_NOT_SAVE_TO_EEPROM_ERR = 14
32
+ INVALID_CHANNEL_ID_ERR = 15
33
+ CANNOT_SET_BUILT_EVENT_ERR = 16
34
+ INVALID_RUN_LEVEL_ERR = 17
35
+
36
+ class EventID(IntEnum):
37
+ EVT_NO_EVENT = 0
38
+ EVT_STATUS_UPDATE = 1
39
+ EVT_START_STIMULATION = 2
40
+ EVT_STOP_STIMULATION = 3
41
+ EVT_STOP_BUTTON01_PRESSED = 4
42
+ EVT_STOP_BUTTON01_RELEASED = 5
43
+ EVT_STOP_BUTTON02_PRESSED = 6
44
+ EVT_STOP_BUTTON02_RELEASED = 7
45
+ EVT_MAX_VAS_SCORED = 8
46
+ EVT_RESPONSE_DISCONNECTED = 9
47
+ EVT_RESPONSE_CONNECTED = 10
48
+ EVT_EMERGENCY_ACTIVATED = 11
49
+ EVT_EMERGENCY_DEACTIVATED = 12
50
+ EVT_WAVEFORMS_COMPLETED = 13
51
+ EVT_TIMELIMIT_EXCEEDED = 14
52
+ EVT_COMM_WATCHDOG_TRIGGERED = 15
53
+ EVT_CUFF01_OUT_OF_COMPLIANCE = 16
54
+ EVT_CUFF02_OUT_OF_COMPLIANCE = 17
55
+ EVT_SUPPLY_PRESSURE_LOW = 18
56
+ EVT_12V_POWER_OFF = 19
57
+ EVT_12V_POWER_ON = 20
58
+ EVT_AIR_LEAK = 21
59
+ EVT_TRIG_IN = 22
60
+
61
+ class OperatingMode(IntEnum):
62
+ RESPONSE_ENABLED = 0
63
+ RESPONSE_DISABLED = 1
64
+
65
+ class StopCondition(IntEnum):
66
+ STOPCOND_NO_CONDITION = 0
67
+ STOPCOND_STOP_BUTTON_PRESSED = 1
68
+ STOPCOND_MAXIMAL_VAS_SCORED = 2
69
+ STOPCOND_STIMULATION_COMPLETED = 3
70
+ STOPCOND_MAXIMAL_TIME_EXCEEDED = 4
71
+ STOPCOND_VASMETER_DISCONNECTED = 5
72
+ STOPCOND_EMERGENCY_STOP_ACTIVATED = 6
73
+ STOPCOND_CONTROL_SOFTWARE = 7
74
+ STOPCOND_OUT_OF_COMPLIANCE = 8
75
+ STOPCOND_COMM_WATCHDOG = 9
76
+ STOPCOND_12V_POWER_OFF = 10
77
+ STOPCOND_SUPPLY_PRESSURE_LOW = 11
78
+
79
+ class StopCriterion(IntEnum):
80
+ STOP_CRITERION_ON_BUTTON_VAS = 0
81
+ STOP_CRITERION_ON_BUTTON_PRESSED = 1
82
+ STOP_CRITERION_ON_BUTTON_RELEASED = 2
83
+
84
+ class WaveformInstructionType(IntEnum):
85
+ TRIG = 0x00
86
+ INC = 0x01
87
+ DEC = 0x02
88
+ STEP = 0x03
@@ -0,0 +1,13 @@
1
+ from .set_waveform_program import SetWaveformProgram
2
+ from .start_stimulation import StartStimulation
3
+ from .stop_stimulation import StopStimulation
4
+ from .set_operating_mode import SetOperatingMode
5
+ from .clear_waveform_programs import ClearWaveformPrograms
6
+
7
+ __all__ = [
8
+ "SetWaveformProgram",
9
+ "StartStimulation",
10
+ "StopStimulation",
11
+ "SetOperatingMode",
12
+ "ClearWaveformPrograms",
13
+ ]
@@ -0,0 +1,20 @@
1
+ from labbench_comm.protocols.device_function import DeviceFunction
2
+ from labbench_comm.protocols.function_dispatcher import FunctionDispatcher
3
+
4
+
5
+ class ClearWaveformPrograms(DeviceFunction):
6
+ @property
7
+ def code(self) -> int:
8
+ return 0x21
9
+
10
+ def __init__(self) -> None:
11
+ super().__init__(request_length=0, response_length=0)
12
+
13
+ def create_dispatcher(self) -> FunctionDispatcher:
14
+ return FunctionDispatcher(self.code, lambda: ClearWaveformPrograms())
15
+
16
+ def dispatch(self, listener):
17
+ return listener.accept(self)
18
+
19
+ def __str__(self) -> str:
20
+ return "[0x21] Clear Waveform Programs"
@@ -0,0 +1,42 @@
1
+ from labbench_comm.protocols.device_function import DeviceFunction
2
+ from labbench_comm.protocols.function_dispatcher import FunctionDispatcher
3
+ from labbench_comm.devices.cpar.definitions import OperatingMode
4
+
5
+
6
+ class SetOperatingMode(DeviceFunction):
7
+ @property
8
+ def code(self) -> int:
9
+ return 0x20
10
+
11
+ def __init__(self) -> None:
12
+ super().__init__(request_length=1, response_length=0)
13
+ self.mode = OperatingMode.RESPONSE_ENABLED
14
+
15
+ # ------------------------------------------------------------------
16
+ # Dispatcher
17
+ # ------------------------------------------------------------------
18
+
19
+ def create_dispatcher(self) -> FunctionDispatcher:
20
+ return FunctionDispatcher(self.code, lambda: SetOperatingMode())
21
+
22
+ def dispatch(self, listener):
23
+ return listener.accept(self)
24
+
25
+ # ------------------------------------------------------------------
26
+ # Properties
27
+ # ------------------------------------------------------------------
28
+
29
+ @property
30
+ def mode(self) -> OperatingMode:
31
+ return OperatingMode(self.request.get_byte(0))
32
+
33
+ @mode.setter
34
+ def mode(self, value: OperatingMode) -> None:
35
+ self.request.insert_byte(0, int(value))
36
+
37
+ # ------------------------------------------------------------------
38
+ # Representation
39
+ # ------------------------------------------------------------------
40
+
41
+ def __str__(self) -> str:
42
+ return "[0x20] Set Operating Mode"