File: mksinst.py

package info (click to toggle)
python-pymeasure 0.14.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 8,788 kB
  • sloc: python: 47,201; makefile: 155
file content (159 lines) | stat: -rw-r--r-- 6,063 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#
# This file is part of the PyMeasure package.
#
# Copyright (c) 2013-2024 PyMeasure Developers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

from re import compile

from pymeasure.instruments import Channel, Instrument
from pymeasure.instruments.validators import strict_discrete_set


class RelayChannel(Channel):
    """
    Settings of the optionally included setpoint relay.

    The relay is energized either below or above the setpoint depending on the
    'direction' property. The relay is de-energized when the reset value is
    crossed in the opposite direction.

    Note that device by default uses an auto hysteresis setting of 10% of the
    setpoint value that overwrites the current reset value whenever the setpoint
    value or direction is changed. If other hysteresis value than 10% is
    required, first set the setpoint value and direction before setting the
    reset value.
    """
    status = Channel.measurement(
        "SS{ch}?",
        """Get the setpoint relay status""",
        values={True: "SET", False: "CLEAR"},
    )

    setpoint = Channel.control(
        "SP{ch}?", "SP{ch}!%s",
        """Control the relay switch setpoint""",
        check_set_errors=True,
    )

    resetpoint = Channel.control(
        "SH{ch}?", "SH{ch}!%s",
        """Control the relay switch off value""",
        check_set_errors=True,
    )

    direction = Channel.control(
        "SD{ch}?", "SD{ch}!%s",
        """Control the switching direction""",
        validator=strict_discrete_set,
        values=["ABOVE", "BELOW"],
        check_set_errors=True,
    )


class MKSInstrument(Instrument):
    """Abstract MKS Instrument

    Connection to the device is made through an RS232/RS485 serial connection.
    The communication protocol of these devices is as follows:

    Query: '@<aaa><Command>?;FF' with the response '@<aaa>ACK<Response>;FF'
    Set command: '@<aaa><Command>!<parameter>;FF' with the response '@<aaa>ACK<Response>;FF'
    Above <aaa> is an address from 001 to 254 which can be specified upon
    initialization. Since ';FF' is not supported by pyvisa as terminator this
    class overloads the device communication methods.

    :param adapter: pyvisa resource name of the instrument or adapter instance
    :param string name: The name of the instrument.
    :param address: device address included in every message to the instrument
                    (default=253)
    :param kwargs: Any valid key-word argument for Instrument
    """

    def __init__(self, adapter, name="MKS Instrument", address=253, **kwargs):
        super().__init__(
            adapter,
            name,
            includeSCPI=False,
            read_termination=";",  # in reality its ";FF"
            # which is, however, invalid for pyvisa. Therefore extra bytes have to
            # be read in the read() method and the terminators are hardcoded here.
            write_termination=";FF",
            **kwargs
        )
        self.address = address
        # compiled regular expression for finding numerical values in reply strings
        self._re_response = compile(fr"@{self.address:03d}(?P<ack>ACK)?(?P<msg>.*)")

    def _extract_reply(self, reply):
        """ preprocess_reply function which tries to extract <Response> from
        '@<aaa>ACK<Response>;FF'. If <Response> can not be identified the orignal string
        is returned.
        :param reply: reply string
        :returns: string with only the response, or the original string
        """
        rvalue = self._re_response.search(reply)
        if rvalue:
            return rvalue.group('msg')
        return reply

    def _prepend_address(self, cmd):
        """
        create command string by including the device address
        """
        return f"@{self.address:03d}{cmd}"

    def _check_extra_termination(self):
        """
        Check the read termination to correspond to the protocol
        """
        t = super().read_bytes(2)  # read extra termination chars 'FF'
        if t != b'FF':
            raise ValueError(f"unexpected termination string received {t}")

    def read(self):
        """
        Reads from the instrument including the correct termination characters
        """
        ret = super().read()
        self._check_extra_termination()
        return self._extract_reply(ret)

    def write(self, command):
        """
        Write to the instrument including the device address.

        :param command: command string to be sent to the instrument
        """
        super().write(self._prepend_address(command))

    def check_set_errors(self):
        """
        Check reply string for acknowledgement string.
        """
        ret = super().read()  # use super read to get raw reply
        reply = self._re_response.search(ret)
        if reply:
            if reply.group('ack') == 'ACK':
                self._check_extra_termination()
                return []
        # no valid acknowledgement message found
        raise ValueError(f"invalid reply '{ret}' found in check_errors")