Skip to content

Communication Protocol

The pypalmsens.CommProtocol and pypalmsens.CommProtocolAsync classes provide an interface to exchange messages with your device using the Communication Protocol. You can use the Communication Protocol to directly query/manipulate the state of your device, e.g. setting registers, file operations, and sending scripts. The interface is of the physical connection type (e.g. serial port, USB, Bluetooth).

The Communication Protocol is supported by all MethodSCRIPT-capable instruments:

For more information how to use these classes, see the documentation here.

Classes:

pypalmsens.CommProtocol

CommProtocol(instrument: Instrument)

Communication interface for MethodSCRIPT instruments.

This class provides high-level communication methods that are independent of the physical connection type (e.g., serial port, USB, Bluetooth). The low-level communication primitives are provided by an instrument object.

Methods:

  • abort

    Abort any currently running script or measurement and wait for completion.

  • close

    Close device connection.

  • flush

    Send a blank line to the device and read its response.

  • get_communication_capabilities

    Retrieve which communication commands are available on this instrument.

  • get_methodscript_capabilities

    Retrieve which MethodSCRIPT features are available on this instrument.

  • lines

    Yield response chunks until a timeout occurs or no more data arrives.

  • open

    Open device connection.

  • query

    Send a command and return its response in a single call.

  • read

    Read the next available chunk from the instrument's input buffer.

  • read_until

    Read lines from the device until a termination sequence is found.

  • run_methodscript

    Load and execute a MethodSCRIPT on the instrument.

  • wait_until

    Wait until a response line starting with prefix arrives.

  • write

    Write a command or data to the instrument.

Attributes:

  • delay (float) –

    Pause (in seconds) between writing a command and reading its response.

  • history (deque[str]) –

    Response history (defaults to last 100 responses).

  • instrument (Instrument) –

    Instrument handle.

  • timeout (float) –

    Maximum time (in seconds) to wait for a response before timing out.

Source code in src/pypalmsens/_instruments/comm_protocol.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def __init__(self, instrument: Instrument):
    self.instrument: Instrument = instrument
    """Instrument handle."""

    self._device: PalmSens.Devices.Device = self.instrument.device
    """Low-level device implementing low-level communication primitives."""

    self.timeout: float = 10.0  # s
    """Maximum time (in seconds) to wait for a response before timing out."""

    self.delay: float = 0.1  # s
    """Pause (in seconds) between writing a command and reading its response.
    This delay is necessary to ensure the device has processed the command.
    Adjust based on your specific hardware and connection type."""

    self.history: deque[str] = deque(maxlen=100)
    """Response history (defaults to last 100 responses)."""

delay instance-attribute

delay: float = 0.1

Pause (in seconds) between writing a command and reading its response. This delay is necessary to ensure the device has processed the command. Adjust based on your specific hardware and connection type.

history instance-attribute

history: deque[str] = deque(maxlen=100)

Response history (defaults to last 100 responses).

instrument instance-attribute

instrument: Instrument = instrument

Instrument handle.

timeout instance-attribute

timeout: float = 10.0

Maximum time (in seconds) to wait for a response before timing out.

abort

abort()

Abort any currently running script or measurement and wait for completion.

This method sends the abort signal to the instrument. If a script was still running, it will wait for it to complete. Note that this could take a while, depending on the measurement that was running.

Source code in src/pypalmsens/_instruments/comm_protocol.py
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
def abort(self):
    """Abort any currently running script or measurement and wait for completion.

    This method sends the abort signal to the instrument. If a script was
    still running, it will wait for it to complete.
    Note that this could take a while, depending on the measurement that
    was running.
    """
    _ = self.flush()

    try:
        response = self.query('Z')
    except CommProtocolError as exc:
        if exc.error_code != '0006':
            raise

        time.sleep(0.1)
    else:
        if response == 'Z\n':
            _ = self.read_until('\n\n')

close

close() -> None

Close device connection.

Source code in src/pypalmsens/_instruments/comm_protocol.py
88
89
90
def close(self) -> None:
    """Close device connection."""
    self._device.Close()

flush

flush()

Send a blank line to the device and read its response.

This does not modify the write buffer. It sends an empty command and reads whatever response follows.

Returns:

  • str

    The response from the device after sending '\n'.

Source code in src/pypalmsens/_instruments/comm_protocol.py
365
366
367
368
369
370
371
372
373
374
375
376
377
def flush(self):
    """Send a blank line to the device and read its response.

    This does not modify the write buffer. It sends
    an empty command and reads whatever response follows.

    Returns
    -------
    str
        The response from the device after sending `'\\n'`.
    """
    self.write('\n')
    return self.read_until('\n')

get_communication_capabilities

get_communication_capabilities() -> tuple[str, ...]

Retrieve which communication commands are available on this instrument.

Returns a set of commands part of the communication protocol that are supported by the device's firmware.

Returns:

  • tuple[str, ...]

    Tuple of supported commands for the instrument.

Source code in src/pypalmsens/_instruments/comm_protocol.py
351
352
353
354
355
356
357
358
359
360
361
362
363
def get_communication_capabilities(self) -> tuple[str, ...]:
    """Retrieve which communication commands are available on this instrument.

    Returns a set of commands part of the communication protocol that
    are supported by the device's firmware.

    Returns
    -------
    tuple[str, ...]
        Tuple of supported commands for the instrument.
    """
    response = self.query('CC')
    return parse_capabilities(response, mapping=COMMUNICATION_CAPABILITIES)

get_methodscript_capabilities

get_methodscript_capabilities() -> tuple[str, ...]

Retrieve which MethodSCRIPT features are available on this instrument.

Returns a set of command names that are licensed and supported by the connected instrument's hardware and firmware.

Returns:

  • tuple[str, ...]

    Tuple of available MethodSCRIPT command names.

Source code in src/pypalmsens/_instruments/comm_protocol.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
def get_methodscript_capabilities(self) -> tuple[str, ...]:
    """Retrieve which MethodSCRIPT features are available on this instrument.

    Returns a set of command names that are licensed
    and supported by the connected instrument's hardware and firmware.

    Returns
    -------
    tuple[str, ...]
        Tuple of available MethodSCRIPT command names.
    """

    response = self.query('CM')
    return parse_capabilities(response, mapping=METHODSCRIPT_CAPABILITIES)

lines

lines(timeout: float | None = None, delay: float | None = None) -> Generator[str, None, None]

Yield response chunks until a timeout occurs or no more data arrives.

This is a generator that continuously reads from the device buffer, yielding chunks as they arrive. It stops when the elapsed time between responses exceeds timeout.

Parameters:

  • timeout

    (float, default: None ) –

    Maximum total time (in seconds) between responses. Defaults to self.timeout.

  • delay

    (float, default: None ) –

    Pause (in seconds) between read attempts when no data is available. Defaults to self.delay.

Yields:

  • response ( str ) –

    Response chunks as they arrive from the device.

Source code in src/pypalmsens/_instruments/comm_protocol.py
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
160
161
162
163
164
165
166
167
168
169
170
def lines(
    self,
    timeout: float | None = None,
    delay: float | None = None,
) -> Generator[str, None, None]:
    """Yield response chunks until a timeout occurs or no more data arrives.

    This is a generator that continuously reads from the device buffer,
    yielding chunks as they arrive.
    It stops when the elapsed time between responses exceeds `timeout`.

    Parameters
    ----------
    timeout : float, optional
        Maximum total time (in seconds) between responses.
        Defaults to `self.timeout`.
    delay : float, optional
        Pause (in seconds) between read attempts when no data is available.
        Defaults to `self.delay`.

    Yields
    ------
    response : str
        Response chunks as they arrive from the device.
    """
    delay = delay or self.delay
    timeout = timeout or self.timeout

    deadline = time.monotonic() + timeout

    while True:
        response = self.read()

        if response:
            match = ERROR_PATTERN.match(response)

            if match:
                error_code = match.group(1) + match.group(2).strip()
                raise CommProtocolError(error_code=error_code)

            yield response
            deadline = time.monotonic() + timeout

        if time.monotonic() > deadline:
            raise TimeoutError('Timed out waiting for response')

        if not response:
            time.sleep(delay)

open

open() -> None

Open device connection.

Source code in src/pypalmsens/_instruments/comm_protocol.py
84
85
86
def open(self) -> None:
    """Open device connection."""
    self._device.Open()

query

query(command: str, end: str | None = None, delay: float | None = None) -> str

Send a command and return its response in a single call.

This is the primary method for interactive communication with the instrument. It writes the command to the device, waits for completion, reads the full response, and returns it as a string.

For commands that run for a long time (e.g. scripts), this method will block until the script completes or times out.

Parameters:

  • command

    (str) –

    The command to send (e.g., 'i' to get the serial number). If command does not end with '\n', one is automatically added.

  • end

    (str, default: None ) –

    The termination character(s) that mark the end of the response.

    If None, a lookup table determines the appropriate terminator based on the command. Most commands use '\n'. Commands with variable-length responses (e.g. scripts) use '\n\n'.

  • delay

    (float, default: None ) –

    Pause (in seconds) between read attempts. Defaults to self.delay.

Returns:

  • response ( str ) –

    The complete response from the device.

Source code in src/pypalmsens/_instruments/comm_protocol.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
def query(
    self,
    command: str,
    end: str | None = None,
    delay: float | None = None,
) -> str:
    """Send a command and return its response in a single call.

    This is the primary method for interactive communication with the
    instrument. It writes the command to the device, waits for completion,
    reads the full response, and returns it as a string.

    For commands that run for a long time (e.g. scripts), this
    method will block until the script completes or times out.

    Parameters
    ----------
    command : str
        The command to send (e.g., 'i' to get the serial number).
        If `command` does not end with `'\\n'`, one is automatically added.
    end : str, optional
        The termination character(s) that mark the end of the response.

        If `None`, a lookup table determines the appropriate terminator
        based on the command. Most commands use `'\\n'`. Commands with
        variable-length responses (e.g. scripts) use `'\\n\\n'`.
    delay : float, optional
        Pause (in seconds) between read attempts. Defaults to `self.delay`.

    Returns
    -------
    response : str
        The complete response from the device.
    """
    delay = delay or self.delay

    if not end:
        try:
            func, *_ = command.split(maxsplit=1)
        except ValueError:
            func = ''

        end = NEWLINE_TERMINATORS.get(func, '\n')

    if not command.endswith('\n'):
        command = f'{command}\n'

    self.write(command)

    prefix = command[0]

    response = self.wait_until(prefix)

    if not response.endswith(end):
        response += self.read_until(end=end, delay=delay)

    return response.removeprefix(prefix).removeprefix('\n').removesuffix('\n')

read

read() -> str

Read the next available chunk from the instrument's input buffer.

This method does not block. It returns immediately with whatever data is currently available in the buffer.

Returns:

  • response ( str ) –

    The next response chunk, or an empty string ('') if the buffer contains no data. Each read is recorded in self.history for debugging and inspection.

Source code in src/pypalmsens/_instruments/comm_protocol.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def read(self) -> str:
    """Read the next available chunk from the instrument's input buffer.

    This method does not block. It returns immediately with whatever
    data is currently available in the buffer.

    Returns
    -------
    response : str
        The next response chunk, or an empty string ('') if the buffer
        contains no data. Each read is recorded in `self.history` for
        debugging and inspection.
    """
    response = self._device.Read()

    if response:
        self.history.append(response)

    return response

read_until

read_until(end: str = '\n', delay: float | None = None) -> str

Read lines from the device until a termination sequence is found.

    Continuously reads responses and concatenates them until
    `end` appears.
    Parameters
    end : str
        The termination sequence that marks the end of the response.
        Most commands use '

'. Scripts and variable-length responses typically use '

'. delay : float, optional Pause (in seconds) between read attempts. Defaults to self.delay.

    Returns
    response : str
        Accumulated response up to and including the termination sequence.
    Raises
    MethodScriptRuntimeError
        If the device returns an error response during reading.
Source code in src/pypalmsens/_instruments/comm_protocol.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def read_until(
    self,
    end: str = '\n',
    delay: float | None = None,
) -> str:
    """Read lines from the device until a termination sequence is found.

    Continuously reads responses and concatenates them until
    `end` appears.

    Parameters
    ----------
    end : str
        The termination sequence that marks the end of the response.
        Most commands use '\n'. Scripts and variable-length responses
        typically use '\n\n'.
    delay : float, optional
        Pause (in seconds) between read attempts. Defaults to `self.delay`.

    Returns
    -------
    response : str
        Accumulated response up to and including the termination sequence.

    Raises
    ------
    MethodScriptRuntimeError
        If the device returns an error response during reading.
    """
    buffer: list[str] = []

    for line in self.lines(delay=delay):
        if line:
            buffer.append(line)

        if line.endswith(end):
            break

    response = ''.join(buffer)

    return response

run_methodscript

run_methodscript(script: str) -> str

Load and execute a MethodSCRIPT on the instrument.

    MethodSCRIPTs are scripts that run directly on the PalmSens device,
    This method uploads the script, runs it to completion,
    and returns any output produced by the script.

    See the MethodSCRIPT documentation for more information.
    Parameters
    script : str
        The MethodSCRIPT to run. The entire script must end
        with exactly one newline ('

').

    Returns
    str
        Output produced by the running script, if any.
Source code in src/pypalmsens/_instruments/comm_protocol.py
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
def run_methodscript(self, script: str) -> str:
    """Load and execute a MethodSCRIPT on the instrument.

    MethodSCRIPTs are scripts that run directly on the PalmSens device,
    This method uploads the script, runs it to completion,
    and returns any output produced by the script.

    See the MethodSCRIPT documentation for more information.

    Parameters
    ----------
    script : str
        The MethodSCRIPT to run. The entire script must end
        with exactly one newline ('\n').

    Returns
    -------
    str
        Output produced by the running script, if any.
    """
    script = script.rstrip('\n')

    return self.query(f'e\n{script}\n\n')

wait_until

wait_until(prefix: str, timeout: float | None = None) -> str

Wait until a response line starting with prefix arrives.

When you send a command, the device echoes back its first character before returning the actual result. Use this method to wait for that echo.

Parameters:

  • prefix

    (str) –

    The first character of an expected response line. This usually matches the command abbreviation (e.g., 'E' for a measurement start).

  • timeout

    (float, default: None ) –

    Maximum time (in seconds) to wait before raising TimeoutError. Defaults to self.timeout.

Returns:

  • response ( str ) –

    The response chunk that starts with prefix, including its termination character.

Raises:

  • TimeoutError

    If no matching line arrives within the timeout period.

Source code in src/pypalmsens/_instruments/comm_protocol.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def wait_until(self, prefix: str, timeout: float | None = None) -> str:
    """Wait until a response line starting with `prefix` arrives.

    When you send a command, the device echoes back its first character
    before returning the actual result. Use this method to wait for
    that echo.

    Parameters
    ----------
    prefix : str
        The first character of an expected response line. This usually
        matches the command abbreviation (e.g., 'E' for a measurement start).
    timeout : float, optional
        Maximum time (in seconds) to wait before raising `TimeoutError`.
        Defaults to `self.timeout`.

    Returns
    -------
    response : str
        The response chunk that starts with `prefix`, including
        its termination character.

    Raises
    ------
    TimeoutError
        If no matching line arrives within the timeout period.
    """
    timeout = timeout or self.timeout

    deadline = time.monotonic() + timeout

    for response in self.lines():
        if response.startswith(prefix):
            return response

        if time.monotonic() > deadline:
            break

    raise TimeoutError(f'Timed out waiting for response starting with {prefix!r}')

write

write(data: str)

Write a command or data to the instrument.

Parameters:

  • data

    (str) –

    Command or data to send. To submit a command for execution, append a newline character ('\n') to the end of the string.

Source code in src/pypalmsens/_instruments/comm_protocol.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def write(self, data: str):
    """Write a command or data to the instrument.

    Parameters
    ----------
    data : str
        Command or data to send. To submit a command for execution,
        append a newline character (`'\\n'`) to the end of the string.
    """
    self._device.Write(data)

pypalmsens.CommProtocolAsync

CommProtocolAsync(instrument: Instrument)

Communication interface for MethodSCRIPT instruments.

This class provides high-level communication methods that are independent of the physical connection type (e.g., serial port, USB, Bluetooth). The low-level communication primitives are provided by an instrument object.

Methods:

  • abort

    Abort any currently running script or measurement and wait for completion.

  • close

    Close device connection.

  • flush

    Send a blank line to the device and read its response.

  • get_communication_capabilities

    Retrieve which communication commands are available on this instrument.

  • get_methodscript_capabilities

    Retrieve which MethodSCRIPT features are available on this instrument.

  • lines

    Yield response chunks until a timeout occurs or no more data arrives.

  • open

    Open device connection.

  • query

    Send a command and return its response in a single call.

  • read

    Read the next available chunk from the instrument's input buffer.

  • read_until

    Read lines from the device until a termination sequence is found.

  • run_methodscript

    Load and execute a MethodSCRIPT on the instrument.

  • wait_until

    Wait until a response line starting with prefix arrives.

  • write

    Write a command or data to the instrument.

Attributes:

  • delay (float) –

    Pause (in seconds) between writing a command and reading its response.

  • history (deque[str]) –

    Response history (defaults to last 100 responses).

  • instrument (Instrument) –

    Instrument handle.

  • timeout (float) –

    Maximum time (in seconds) to wait for a response before timing out.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def __init__(self, instrument: Instrument):
    self.instrument: Instrument = instrument
    """Instrument handle."""

    self._device: PalmSens.Devices.Device = self.instrument.device
    """Low-level device implementing low-level communication primitives."""

    self.timeout: float = 10.0  # s
    """Maximum time (in seconds) to wait for a response before timing out."""

    self.delay: float = 0.1  # s
    """Pause (in seconds) between writing a command and reading its response.
    This delay is necessary to ensure the device has processed the command.
    Adjust based on your specific hardware and connection type."""

    self.history: deque[str] = deque(maxlen=100)
    """Response history (defaults to last 100 responses)."""

delay instance-attribute

delay: float = 0.1

Pause (in seconds) between writing a command and reading its response. This delay is necessary to ensure the device has processed the command. Adjust based on your specific hardware and connection type.

history instance-attribute

history: deque[str] = deque(maxlen=100)

Response history (defaults to last 100 responses).

instrument instance-attribute

instrument: Instrument = instrument

Instrument handle.

timeout instance-attribute

timeout: float = 10.0

Maximum time (in seconds) to wait for a response before timing out.

abort async

abort()

Abort any currently running script or measurement and wait for completion.

This method sends the abort signal to the instrument. If a script was still running, it will wait for it to complete. Note that this could take a while, depending on the measurement that was running.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
async def abort(self):
    """Abort any currently running script or measurement and wait for completion.

    This method sends the abort signal to the instrument. If a script was
    still running, it will wait for it to complete.
    Note that this could take a while, depending on the measurement that
    was running.
    """
    _ = await self.flush()

    try:
        response = await self.query('Z')
    except CommProtocolError as exc:
        if exc.error_code != '0006':
            raise

        await asyncio.sleep(0.1)
    else:
        if response == 'Z\n':
            _ = await self.read_until('\n\n')

close async

close() -> None

Close device connection.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
62
63
64
async def close(self) -> None:
    """Close device connection."""
    await self.instrument._close_async()

flush async

flush()

Send a blank line to the device and read its response.

This does not modify the write buffer. It sends an empty command and reads whatever response follows.

Returns:

  • str

    The response from the device after sending '\n'.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
339
340
341
342
343
344
345
346
347
348
349
350
351
async def flush(self):
    """Send a blank line to the device and read its response.

    This does not modify the write buffer. It sends
    an empty command and reads whatever response follows.

    Returns
    -------
    str
        The response from the device after sending `'\\n'`.
    """
    await self.write('\n')
    return await self.read_until('\n')

get_communication_capabilities async

get_communication_capabilities() -> tuple[str, ...]

Retrieve which communication commands are available on this instrument.

Returns a set of commands part of the communication protocol that are supported by the device's firmware.

Returns:

  • tuple[str, ...]

    Set of supported commands for the instrument.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
325
326
327
328
329
330
331
332
333
334
335
336
337
async def get_communication_capabilities(self) -> tuple[str, ...]:
    """Retrieve which communication commands are available on this instrument.

    Returns a set of commands part of the communication protocol that
    are supported by the device's firmware.

    Returns
    -------
    tuple[str, ...]
        Set of supported commands for the instrument.
    """
    response = await self.query('CC')
    return parse_capabilities(response, mapping=COMMUNICATION_CAPABILITIES)

get_methodscript_capabilities async

get_methodscript_capabilities() -> tuple[str, ...]

Retrieve which MethodSCRIPT features are available on this instrument.

Returns a set of command names that are licensed and supported by the connected instrument's hardware and firmware.

Returns:

  • tuple[str, ...]

    Set of available MethodSCRIPT command names.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
async def get_methodscript_capabilities(self) -> tuple[str, ...]:
    """Retrieve which MethodSCRIPT features are available on this instrument.

    Returns a set of command names that are licensed
    and supported by the connected instrument's hardware and firmware.

    Returns
    -------
    tuple[str, ...]
        Set of available MethodSCRIPT command names.
    """

    response = await self.query('CM')
    return parse_capabilities(response, mapping=METHODSCRIPT_CAPABILITIES)

lines async

lines(timeout: float | None = None, delay: float | None = None) -> AsyncIterator[str]

Yield response chunks until a timeout occurs or no more data arrives.

This is an async generator that continuously reads from the device buffer, yielding chunks as they arrive. It stops when the elapsed time between responses exceeds timeout.

Parameters:

  • timeout

    (float, default: None ) –

    Maximum total time (in seconds) between responses. Defaults to self.timeout.

  • delay

    (float, default: None ) –

    Pause (in seconds) between read attempts when no data is available. Defaults to self.delay.

Yields:

  • response ( str ) –

    Response chunks as they arrive from the device.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
 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
async def lines(
    self,
    timeout: float | None = None,
    delay: float | None = None,
) -> AsyncIterator[str]:
    """Yield response chunks until a timeout occurs or no more data arrives.

    This is an async generator that continuously reads from the device buffer,
    yielding chunks as they arrive.
    It stops when the elapsed time between responses exceeds `timeout`.

    Parameters
    ----------
    timeout : float, optional
        Maximum total time (in seconds) between responses.
        Defaults to `self.timeout`.
    delay : float, optional
        Pause (in seconds) between read attempts when no data is available.
        Defaults to `self.delay`.

    Yields
    ------
    response : str
        Response chunks as they arrive from the device.
    """
    delay = delay or self.delay
    timeout = timeout or self.timeout

    deadline = time.monotonic() + timeout

    while True:
        response = await self.read()

        if response:
            match = ERROR_PATTERN.match(response)

            if match:
                error_code = match.group(1) + match.group(2).strip()
                raise CommProtocolError(error_code=error_code)

            yield response
            deadline = time.monotonic() + timeout

        if time.monotonic() > deadline:
            raise TimeoutError('Timed out waiting for response')

        if not response:
            await asyncio.sleep(delay)

open async

open() -> None

Open device connection.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
58
59
60
async def open(self) -> None:
    """Open device connection."""
    await self.instrument._open_async()

query async

query(command: str, end: str | None = None, delay: float | None = None) -> str

Send a command and return its response in a single call.

This is the primary method for interactive communication with the instrument. It writes the command to the device, waits for completion, reads the full response, and returns it as a string.

For commands that run for a long time (e.g. scripts), this method will block until the script completes or times out.

Parameters:

  • command

    (str) –

    The command to send (e.g., 'i' to get the serial number). If command does not end with '\n', one is automatically added.

  • end

    (str, default: None ) –

    The termination character(s) that mark the end of the response.

    If None, a lookup table determines the appropriate terminator based on the command. Most commands use '\n'. Commands with variable-length responses (e.g. scripts) use '\n\n'.

  • delay

    (float, default: None ) –

    Pause (in seconds) between read attempts. Defaults to self.delay.

Returns:

  • response ( str ) –

    The complete response from the device, including termination characters.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
async def query(
    self,
    command: str,
    end: str | None = None,
    delay: float | None = None,
) -> str:
    """Send a command and return its response in a single call.

    This is the primary method for interactive communication with the
    instrument. It writes the command to the device, waits for completion,
    reads the full response, and returns it as a string.

    For commands that run for a long time (e.g. scripts), this
    method will block until the script completes or times out.

    Parameters
    ----------
    command : str
        The command to send (e.g., 'i' to get the serial number).
        If `command` does not end with `'\\n'`, one is automatically added.
    end : str, optional
        The termination character(s) that mark the end of the response.

        If `None`, a lookup table determines the appropriate terminator
        based on the command. Most commands use `'\\n'`. Commands with
        variable-length responses (e.g. scripts) use `'\\n\\n'`.
    delay : float, optional
        Pause (in seconds) between read attempts. Defaults to `self.delay`.

    Returns
    -------
    response : str
        The complete response from the device, including termination characters.
    """
    delay = delay or self.delay

    if not end:
        try:
            func, *_ = command.split(maxsplit=1)
        except ValueError:
            func = ''

        end = NEWLINE_TERMINATORS.get(func, '\n')

    if not command.endswith('\n'):
        command = f'{command}\n'

    await self.write(command)

    prefix = command[0]

    response = await self.wait_until(prefix)

    if not response.endswith(end):
        response += await self.read_until(end=end, delay=delay)

    return response.removeprefix(prefix).removeprefix('\n').removesuffix('\n')

read async

read() -> str

Read the next available chunk from the instrument's input buffer.

This method does not block. It returns immediately with whatever data is currently available in the buffer.

Returns:

  • response ( str ) –

    The next response chunk, or an empty string ('') if the buffer contains no data. Each read is recorded in self.history for debugging and inspection.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
async def read(self) -> str:
    """Read the next available chunk from the instrument's input buffer.

    This method does not block. It returns immediately with whatever
    data is currently available in the buffer.

    Returns
    -------
    response : str
        The next response chunk, or an empty string ('') if the buffer
        contains no data. Each read is recorded in `self.history` for
        debugging and inspection.
    """
    response: str = await create_future(self._device.ReadAsync())

    if response:
        self.history.append(response)

    return response

read_until async

read_until(end: str = '\n', delay: float | None = None) -> str

Read lines from the device until a termination sequence is found.

    Continuously reads responses and concatenates them until
    `end` appears.
    Parameters
    end : str
        The termination sequence that marks the end of the response.
        Most commands use '

'. Scripts and variable-length responses typically use '

'. delay : float, optional Pause (in seconds) between read attempts. Defaults to self.delay.

    Returns
    response : str
        Accumulated response up to and including the termination sequence.
    Raises
    MethodScriptRuntimeError
        If the device returns an error response during reading.
Source code in src/pypalmsens/_instruments/comm_protocol_async.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
async def read_until(
    self,
    end: str = '\n',
    delay: float | None = None,
) -> str:
    """Read lines from the device until a termination sequence is found.

    Continuously reads responses and concatenates them until
    `end` appears.

    Parameters
    ----------
    end : str
        The termination sequence that marks the end of the response.
        Most commands use '\n'. Scripts and variable-length responses
        typically use '\n\n'.
    delay : float, optional
        Pause (in seconds) between read attempts. Defaults to `self.delay`.

    Returns
    -------
    response : str
        Accumulated response up to and including the termination sequence.

    Raises
    ------
    MethodScriptRuntimeError
        If the device returns an error response during reading.
    """
    buffer: list[str] = []

    async for line in self.lines(delay=delay):
        if line:
            buffer.append(line)

        if line.endswith(end):
            break

    response = ''.join(buffer)

    return response

run_methodscript async

run_methodscript(script: str) -> str

Load and execute a MethodSCRIPT on the instrument.

    MethodSCRIPTs are scripts that run directly on the PalmSens device,
    This method uploads the script, runs it to completion,
    and returns any output produced by the script.

    See the MethodSCRIPT documentation for more information.
    Parameters
    script : str
        The MethodSCRIPT to run. The entire script must end
        with exactly one newline ('

').

    Returns
    str
        Output produced by the running script, if any.
Source code in src/pypalmsens/_instruments/comm_protocol_async.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
async def run_methodscript(self, script: str) -> str:
    """Load and execute a MethodSCRIPT on the instrument.

    MethodSCRIPTs are scripts that run directly on the PalmSens device,
    This method uploads the script, runs it to completion,
    and returns any output produced by the script.

    See the MethodSCRIPT documentation for more information.

    Parameters
    ----------
    script : str
        The MethodSCRIPT to run. The entire script must end
        with exactly one newline ('\n').

    Returns
    -------
    str
        Output produced by the running script, if any.
    """
    script = script.rstrip('\n')

    return await self.query(f'e\n{script}\n\n')

wait_until async

wait_until(prefix: str, timeout: float | None = None) -> str

Wait until a response line starting with prefix arrives.

When you send a command, the device echoes back its first character before returning the actual result. Use this method to wait for that echo.

Parameters:

  • prefix

    (str) –

    The first character of an expected response line. This usually matches the command abbreviation (e.g., 'E' for a measurement start).

  • timeout

    (float, default: None ) –

    Maximum time (in seconds) to wait before raising TimeoutError. Defaults to self.timeout.

Returns:

  • response ( str ) –

    The response chunk that starts with prefix, including its termination character.

Raises:

  • TimeoutError

    If no matching line arrives within the timeout period.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
async def wait_until(self, prefix: str, timeout: float | None = None) -> str:
    """Wait until a response line starting with `prefix` arrives.

    When you send a command, the device echoes back its first character
    before returning the actual result. Use this method to wait for
    that echo.

    Parameters
    ----------
    prefix : str
        The first character of an expected response line. This usually
        matches the command abbreviation (e.g., 'E' for a measurement start).
    timeout : float, optional
        Maximum time (in seconds) to wait before raising `TimeoutError`.
        Defaults to `self.timeout`.

    Returns
    -------
    response : str
        The response chunk that starts with `prefix`, including
        its termination character.

    Raises
    ------
    TimeoutError
        If no matching line arrives within the timeout period.
    """
    timeout = timeout or self.timeout

    deadline = time.monotonic() + timeout

    async for response in self.lines():
        if response.startswith(prefix):
            return response

        if time.monotonic() > deadline:
            break

    raise TimeoutError(f'Timed out waiting for response starting with {prefix!r}')

write async

write(data: str)

Write a command or data to the instrument.

Parameters:

  • data

    (str) –

    Command or data to send. To submit a command for execution, append a newline character ('\n') to the end of the string.

Source code in src/pypalmsens/_instruments/comm_protocol_async.py
66
67
68
69
70
71
72
73
74
75
async def write(self, data: str):
    """Write a command or data to the instrument.

    Parameters
    ----------
    data : str
        Command or data to send. To submit a command for execution,
        append a newline character (`'\\n'`) to the end of the string.
    """
    await create_future(self._device.WriteAsync(data))