From 361380251df65579ae22c9965986cfebfa6503d9 Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Fri, 1 Dec 2023 16:57:53 +0000 Subject: [PATCH 01/13] Implement socket communication with LeCroy --- .../scope/lecroy/lecroy_communication.py | 156 +++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index b4eae556..910ca3b4 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -17,8 +17,12 @@ from abc import ABC, abstractmethod import hashlib import logging +import socket +from struct import pack, unpack from typing import Optional +import numpy as np + import pyvisa from scaaml.capture.scope.lecroy.lecroy_waveform import LecroyWaveform @@ -71,7 +75,9 @@ def query_binary_values(self, message: str, datatype="B", container=bytearray) -> bytearray: - """Query binary data.""" + """Query binary data. Beware that the results from socket version might + contain headers which are stripped by pyvisa. + """ def _check_response_template(self): """ Check if the hash of the waveform template matches the supported @@ -185,3 +191,151 @@ def query_binary_values(self, datatype=datatype, container=container, ) + + +class LeCroyCommunicationSocket(LeCroyCommunication): + """Use Python socket to communicate using the TCP/IP (VICP). + ("Utilities > Utilities Setup > Remote" and choose TCPIP).""" + + def __init__(self, ip_address: str, timeout: float = 5.0): + super().__init__( + ip_address=ip_address, + timeout=timeout, + ) + # Header format (see section "VICP Headers"): + # operation: byte + # header_version: byte + # sequence_number: byte + # spare: byte = 0 (reserved for future) + # block_length: long = length of the command (block to be sent) + self._s_leCroyCommandHeader = ">BBBBL" + self._socket: Optional[socket.socket] = None + + @make_custom_exception + def connect(self): + assert self._socket is None + + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.settimeout(self._timeout) + + # establish connection + self._socket.connect((self._ip_address, 1861)) + + # Check if the response template is what LecroyWaveform expects + self._check_response_template() + + @make_custom_exception + def close(self) -> None: + assert self._socket is not None + self._socket.shutdown(socket.SHUT_RDWR) + self._socket.close() + self._socket = None + + @make_custom_exception + def write(self, message: str) -> None: + """Write a message to the oscilloscope. + """ + assert self._socket is not None + self._socket.send(self._format_command(message)) + + @make_custom_exception + def query(self, message: str) -> str: + """Query the oscilloscope (write, read, and decode the answer as a + string). + """ + return self.query_binary_values(message).decode() + + @make_custom_exception + def get_waveform(self, channel: LECROY_CHANNEL_NAME_T) -> LecroyWaveform: + """Get a LecroyWaveform object representing a single waveform. + + Args: + channel (LECROY_CHANNEL_NAME_T): The name of queried channel. + """ + raw_data = self.query_binary_values(f"{channel}:WAVEFORM?") + assert raw_data[:6] == b"ALL,#9" # followed by 9 digits for size + raw_data = raw_data[15:-1] # last is linefeed + return LecroyWaveform(raw_data) + + @make_custom_exception + def query_binary_values(self, message: str, datatype="B", container=None) -> bytearray: + """Query binary data. + + Args: + message (str): Query message. + datatype (str): Ignored. + container: A bytearray is always used. + """ + assert self._socket is not None + + del datatype # ignored + del container # ignored + + self._logger.debug(f"\"{message}\"") + + # Send message + self.write(message) + + # Receive and decode answer + return self._get_raw_response() + + + def _format_command(self, command: str) -> bytearray: + """Method formatting leCroy command. + + Args: + command (str): The command to be formatted for sending over a + socket. + + Returns: Bytearray representation to be directly sent over a socket. + """ + # Compute header for the current command, header: + # operation = DATA | EOI + command_header = pack(self._s_leCroyCommandHeader, 129, 1, 1, 0, + len(command)) + + formatted_command = command_header + command.encode("ascii") + return formatted_command + + + def _get_raw_response(self) -> bytearray: + """Get raw response from the socket. + + Returns: bytearray representation of the response. + """ + response = b"" + + while True: + header = b"" + + # Loop until we get a full header (8 bytes) + while len(header) < 8: + header += self._socket.recv(8 - len(header)) + + # Parse formated response + (operation, + header_version, # unused + sequence_number, # unused + spare, # unused + v_nbTotalBytes) = unpack(self._s_leCroyCommandHeader, header) + + # Delete unused values + del header_version + del sequence_number + del spare + + # Buffer for the current portion of data + buffer = b"" + + # Loop until we get all data + while (len(buffer) < v_nbTotalBytes): + buffer += self._socket.recv(min(v_nbTotalBytes - len(buffer), 8_192)) + + # Accumulate final response + response += buffer + + # Leave the loop when the EOI bit is set + if operation % 2: + break + + return response From 8c48d6286187e8005c02e606e2dac1e242228fdb Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Fri, 1 Dec 2023 17:04:17 +0000 Subject: [PATCH 02/13] [squash] make yapf happy --- .../scope/lecroy/lecroy_communication.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index 910ca3b4..80e64e22 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -258,7 +258,10 @@ def get_waveform(self, channel: LECROY_CHANNEL_NAME_T) -> LecroyWaveform: return LecroyWaveform(raw_data) @make_custom_exception - def query_binary_values(self, message: str, datatype="B", container=None) -> bytearray: + def query_binary_values(self, + message: str, + datatype="B", + container=None) -> bytearray: """Query binary data. Args: @@ -279,7 +282,6 @@ def query_binary_values(self, message: str, datatype="B", container=None) -> byt # Receive and decode answer return self._get_raw_response() - def _format_command(self, command: str) -> bytearray: """Method formatting leCroy command. @@ -292,12 +294,11 @@ def _format_command(self, command: str) -> bytearray: # Compute header for the current command, header: # operation = DATA | EOI command_header = pack(self._s_leCroyCommandHeader, 129, 1, 1, 0, - len(command)) + len(command)) formatted_command = command_header + command.encode("ascii") return formatted_command - def _get_raw_response(self) -> bytearray: """Get raw response from the socket. @@ -313,11 +314,12 @@ def _get_raw_response(self) -> bytearray: header += self._socket.recv(8 - len(header)) # Parse formated response - (operation, - header_version, # unused - sequence_number, # unused - spare, # unused - v_nbTotalBytes) = unpack(self._s_leCroyCommandHeader, header) + ( + operation, + header_version, # unused + sequence_number, # unused + spare, # unused + v_nbTotalBytes) = unpack(self._s_leCroyCommandHeader, header) # Delete unused values del header_version @@ -329,7 +331,8 @@ def _get_raw_response(self) -> bytearray: # Loop until we get all data while (len(buffer) < v_nbTotalBytes): - buffer += self._socket.recv(min(v_nbTotalBytes - len(buffer), 8_192)) + buffer += self._socket.recv( + min(v_nbTotalBytes - len(buffer), 8_192)) # Accumulate final response response += buffer From 260cb935e904a3ff0ae642af0180c7a22f0aa4f0 Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Fri, 1 Dec 2023 17:08:32 +0000 Subject: [PATCH 03/13] [squash] make mypy happy --- scaaml/capture/scope/lecroy/lecroy_communication.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index 80e64e22..f3497600 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -261,13 +261,15 @@ def get_waveform(self, channel: LECROY_CHANNEL_NAME_T) -> LecroyWaveform: def query_binary_values(self, message: str, datatype="B", - container=None) -> bytearray: + container=None) -> bytes: """Query binary data. Args: message (str): Query message. datatype (str): Ignored. container: A bytearray is always used. + + Returns: a bytes representation of the response. """ assert self._socket is not None @@ -282,14 +284,14 @@ def query_binary_values(self, # Receive and decode answer return self._get_raw_response() - def _format_command(self, command: str) -> bytearray: + def _format_command(self, command: str) -> bytes: """Method formatting leCroy command. Args: command (str): The command to be formatted for sending over a socket. - Returns: Bytearray representation to be directly sent over a socket. + Returns: bytes representation to be directly sent over a socket. """ # Compute header for the current command, header: # operation = DATA | EOI @@ -299,11 +301,12 @@ def _format_command(self, command: str) -> bytearray: formatted_command = command_header + command.encode("ascii") return formatted_command - def _get_raw_response(self) -> bytearray: + def _get_raw_response(self) -> bytes: """Get raw response from the socket. - Returns: bytearray representation of the response. + Returns: bytes representation of the response. """ + assert self._socket is not None response = b"" while True: From 63a0cca493116f65e9b6f8959ec55d82f16508cb Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Fri, 1 Dec 2023 17:12:23 +0000 Subject: [PATCH 04/13] [squash] make pylint happy --- .../capture/scope/lecroy/lecroy_communication.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index f3497600..b670a5dc 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -21,8 +21,6 @@ from struct import pack, unpack from typing import Optional -import numpy as np - import pyvisa from scaaml.capture.scope.lecroy.lecroy_waveform import LecroyWaveform @@ -208,7 +206,7 @@ def __init__(self, ip_address: str, timeout: float = 5.0): # sequence_number: byte # spare: byte = 0 (reserved for future) # block_length: long = length of the command (block to be sent) - self._s_leCroyCommandHeader = ">BBBBL" + self._lecroy_command_header = ">BBBBL" self._socket: Optional[socket.socket] = None @make_custom_exception @@ -276,7 +274,7 @@ def query_binary_values(self, del datatype # ignored del container # ignored - self._logger.debug(f"\"{message}\"") + self._logger.debug("\"%s\"", message) # Send message self.write(message) @@ -295,7 +293,7 @@ def _format_command(self, command: str) -> bytes: """ # Compute header for the current command, header: # operation = DATA | EOI - command_header = pack(self._s_leCroyCommandHeader, 129, 1, 1, 0, + command_header = pack(self._lecroy_command_header, 129, 1, 1, 0, len(command)) formatted_command = command_header + command.encode("ascii") @@ -322,7 +320,7 @@ def _get_raw_response(self) -> bytes: header_version, # unused sequence_number, # unused spare, # unused - v_nbTotalBytes) = unpack(self._s_leCroyCommandHeader, header) + total_bytes) = unpack(self._lecroy_command_header, header) # Delete unused values del header_version @@ -333,9 +331,9 @@ def _get_raw_response(self) -> bytes: buffer = b"" # Loop until we get all data - while (len(buffer) < v_nbTotalBytes): + while len(buffer) < total_bytes: buffer += self._socket.recv( - min(v_nbTotalBytes - len(buffer), 8_192)) + min(total_bytes - len(buffer), 8_192)) # Accumulate final response response += buffer From d4d750e218489cea7308909a48ff2377853f9eee Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Mon, 4 Dec 2023 09:44:52 +0000 Subject: [PATCH 05/13] [squash] give credit to Victor --- scaaml/capture/scope/lecroy/lecroy_communication.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index b670a5dc..a51b3d3e 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -11,6 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# +# Based on by code kindly shared by Victor Lomné - NinjaLa """Communication with the LeCroy oscilloscope. Either using pyvisa or socket. """ From cb60a67018966dbfb2b984572feebeaa0b114e1f Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Mon, 4 Dec 2023 09:45:20 +0000 Subject: [PATCH 06/13] [squash] simplify header description --- scaaml/capture/scope/lecroy/lecroy_communication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index a51b3d3e..a3b9251a 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -208,7 +208,7 @@ def __init__(self, ip_address: str, timeout: float = 5.0): # sequence_number: byte # spare: byte = 0 (reserved for future) # block_length: long = length of the command (block to be sent) - self._lecroy_command_header = ">BBBBL" + self._lecroy_command_header = ">4BL" self._socket: Optional[socket.socket] = None @make_custom_exception From 2d966534cc13a725d337911f292dbd9608cc7d36 Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Mon, 4 Dec 2023 09:53:10 +0000 Subject: [PATCH 07/13] [squash] be less explicit with testing for None --- .../scope/lecroy/lecroy_communication.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index a3b9251a..5686d7d9 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -125,7 +125,7 @@ def __init__(self, ip_address: str, timeout: float = 5.0): def connect(self): # For portability and ease of setup we enforce the pure Python backend self._resource_manager = pyvisa.ResourceManager("@py") - assert self._resource_manager is not None + assert self._resource_manager scope_resource = self._resource_manager.open_resource( f"TCPIP::{self._ip_address}::INSTR", @@ -134,7 +134,7 @@ def connect(self): assert isinstance(scope_resource, pyvisa.resources.MessageBasedResource) self._scope = scope_resource - assert self._scope is not None + assert self._scope self._scope.timeout = self._timeout * 1_000 # Convert second to ms self._scope.clear() @@ -143,17 +143,17 @@ def connect(self): @make_custom_exception def close(self) -> None: - assert self._scope is not None + assert self._scope self._scope.before_close() self._scope.close() - assert self._resource_manager is not None + assert self._resource_manager self._resource_manager.close() @make_custom_exception def write(self, message: str) -> None: """Write a message to the oscilloscope. """ - assert self._scope is not None + assert self._scope self._logger.debug("write(message=\"%s\")", message) self._scope.write(message) @@ -162,7 +162,7 @@ def query(self, message: str) -> str: """Query the oscilloscope (write, read, and decode the answer as a string). """ - assert self._scope is not None + assert self._scope self._logger.debug("query(message=\"%s\")", message) return self._scope.query(message).strip() @@ -170,7 +170,7 @@ def query(self, message: str) -> str: def get_waveform(self, channel: LECROY_CHANNEL_NAME_T) -> LecroyWaveform: """Get a LecroyWaveform object representing a single waveform. """ - assert self._scope is not None + assert self._scope return self._scope.query_binary_values( f"{channel}:WAVEFORM?", @@ -184,7 +184,7 @@ def query_binary_values(self, datatype="B", container=bytearray): """Query binary data.""" - assert self._scope is not None + assert self._scope self._logger.debug("query_binary_values(message=\"%s\")", message) return self._scope.query_binary_values( message, @@ -226,7 +226,7 @@ def connect(self): @make_custom_exception def close(self) -> None: - assert self._socket is not None + assert self._socket self._socket.shutdown(socket.SHUT_RDWR) self._socket.close() self._socket = None @@ -235,7 +235,7 @@ def close(self) -> None: def write(self, message: str) -> None: """Write a message to the oscilloscope. """ - assert self._socket is not None + assert self._socket self._socket.send(self._format_command(message)) @make_custom_exception @@ -271,7 +271,7 @@ def query_binary_values(self, Returns: a bytes representation of the response. """ - assert self._socket is not None + assert self._socket del datatype # ignored del container # ignored @@ -306,7 +306,7 @@ def _get_raw_response(self) -> bytes: Returns: bytes representation of the response. """ - assert self._socket is not None + assert self._socket response = b"" while True: From c994dcb591cae5deb275fc151df540250b411d6d Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Mon, 4 Dec 2023 09:55:28 +0000 Subject: [PATCH 08/13] [squash] rstrip instead of always removing right most char --- scaaml/capture/scope/lecroy/lecroy_communication.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index 5686d7d9..3a8aa0a1 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -254,7 +254,8 @@ def get_waveform(self, channel: LECROY_CHANNEL_NAME_T) -> LecroyWaveform: """ raw_data = self.query_binary_values(f"{channel}:WAVEFORM?") assert raw_data[:6] == b"ALL,#9" # followed by 9 digits for size - raw_data = raw_data[15:-1] # last is linefeed + raw_data = raw_data[15:] + raw_data = raw_data.rstrip() # last is linefeed return LecroyWaveform(raw_data) @make_custom_exception From 20ef3076885b50209d4cfe39a982d2fe43e9b1e3 Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Mon, 4 Dec 2023 10:17:43 +0000 Subject: [PATCH 09/13] [squash] use mutable bytearray for better complexity --- .../capture/scope/lecroy/lecroy_communication.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index 3a8aa0a1..9136ba15 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -308,14 +308,14 @@ def _get_raw_response(self) -> bytes: Returns: bytes representation of the response. """ assert self._socket - response = b"" + response = bytearray() while True: - header = b"" + header = bytearray() # Loop until we get a full header (8 bytes) while len(header) < 8: - header += self._socket.recv(8 - len(header)) + header.extend(self._socket.recv(8 - len(header))) # Parse formated response ( @@ -331,18 +331,18 @@ def _get_raw_response(self) -> bytes: del spare # Buffer for the current portion of data - buffer = b"" + buffer = bytearray() # Loop until we get all data while len(buffer) < total_bytes: - buffer += self._socket.recv( - min(total_bytes - len(buffer), 8_192)) + buffer.extend(self._socket.recv( + min(total_bytes - len(buffer), 8_192))) # Accumulate final response - response += buffer + response.extend(buffer) # Leave the loop when the EOI bit is set if operation % 2: break - return response + return bytes(response) From 1db6fb4d82ee057981a27908926b7220e1c7d837 Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Mon, 4 Dec 2023 10:18:30 +0000 Subject: [PATCH 10/13] [squash] use bit operation codestyle --- scaaml/capture/scope/lecroy/lecroy_communication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index 9136ba15..256b350d 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -342,7 +342,7 @@ def _get_raw_response(self) -> bytes: response.extend(buffer) # Leave the loop when the EOI bit is set - if operation % 2: + if operation & 1: break return bytes(response) From 244f041e2af71c8f8c0afc3ce21697c4fcac15a6 Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Mon, 4 Dec 2023 10:22:45 +0000 Subject: [PATCH 11/13] [squash] make yapf happy --- scaaml/capture/scope/lecroy/lecroy_communication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index 256b350d..2afaa76d 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -335,8 +335,8 @@ def _get_raw_response(self) -> bytes: # Loop until we get all data while len(buffer) < total_bytes: - buffer.extend(self._socket.recv( - min(total_bytes - len(buffer), 8_192))) + buffer.extend( + self._socket.recv(min(total_bytes - len(buffer), 8_192))) # Accumulate final response response.extend(buffer) From 35388293bfd83cb33ae1debf79125dfd74cff611 Mon Sep 17 00:00:00 2001 From: kralka Date: Mon, 4 Dec 2023 12:10:24 +0100 Subject: [PATCH 12/13] Update lecroy_communication.py [squash] fix typo --- scaaml/capture/scope/lecroy/lecroy_communication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index 2afaa76d..1e0a48ec 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Based on by code kindly shared by Victor Lomné - NinjaLa +# Based on by code kindly shared by Victor Lomné - NinjaLab """Communication with the LeCroy oscilloscope. Either using pyvisa or socket. """ From cca24d981f04236732929ebed1f593a0a75543ae Mon Sep 17 00:00:00 2001 From: Karel Kral Date: Mon, 4 Dec 2023 11:22:37 +0000 Subject: [PATCH 13/13] [squash] Parse waveform message length --- scaaml/capture/scope/lecroy/lecroy_communication.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scaaml/capture/scope/lecroy/lecroy_communication.py b/scaaml/capture/scope/lecroy/lecroy_communication.py index 1e0a48ec..eb97cbc4 100644 --- a/scaaml/capture/scope/lecroy/lecroy_communication.py +++ b/scaaml/capture/scope/lecroy/lecroy_communication.py @@ -254,8 +254,11 @@ def get_waveform(self, channel: LECROY_CHANNEL_NAME_T) -> LecroyWaveform: """ raw_data = self.query_binary_values(f"{channel}:WAVEFORM?") assert raw_data[:6] == b"ALL,#9" # followed by 9 digits for size + len_raw_data = int(raw_data[6:15]) # length without the header raw_data = raw_data[15:] - raw_data = raw_data.rstrip() # last is linefeed + if len_raw_data + 1 == len(raw_data): + raw_data = raw_data[:-1] # last is linefeed + assert len(raw_data) == len_raw_data return LecroyWaveform(raw_data) @make_custom_exception