From 236501dc56fa9bac33a194935b1e35d20c4dbef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Ejler=20S=C3=B8rensen?= Date: Thu, 2 Nov 2023 14:17:22 +0100 Subject: [PATCH 1/2] drivers/mks_g_series.py many updates: Python 3 only, NAK support, comm and checksum cleanup, read internal temperature --- PyExpLabSys/drivers/mks_g_series.py | 92 +++++++++++++++++++---------- 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/PyExpLabSys/drivers/mks_g_series.py b/PyExpLabSys/drivers/mks_g_series.py index 7611e37a..418a59b1 100644 --- a/PyExpLabSys/drivers/mks_g_series.py +++ b/PyExpLabSys/drivers/mks_g_series.py @@ -3,11 +3,27 @@ import time import logging import serial -from PyExpLabSys.common.supported_versions import python2_and_3 LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) -python2_and_3(__file__) + +NAK_TABLE = { + '01': 'Checksum error', + '10': 'Syntax error', + '11': 'Data length error', + '12': 'Invalid data', + '13': 'Invalid operating mode', + '14': 'Invalid action', + '15': 'Invalid gas', + '16': 'Invalid control mode', + '17': 'Invalid command', + '24': 'Calibration error', + '25': 'Flow too large', + '27': 'Too many gases in gas table', + '28': 'Flow cal error, valve not open', + '98': 'Internal device error', + '99': 'Internal device error', +} class MksGSeries: @@ -20,46 +36,57 @@ def __init__(self, port='/dev/ttyUSB0'): self.ser.bytesize = serial.EIGHTBITS self.ser.stopbits = serial.STOPBITS_ONE - def checksum(self, command, reply=False): + def checksum(self, command): """ Calculate checksum of command """ - if not reply: - com_string = '@' + command - else: - com_string = command - total = 0 - for i in range(0, len(com_string)): - total = total + ord(com_string[i]) - return (hex(total)[-2:]).upper() + com_string = '@' + command + checksum = sum([ord(i) for i in com_string]) + # The following hack deviates from the manual, but has proven to work on two different setups + if 'NAK' in com_string: + checksum += 9 + return hex(checksum)[-2:].upper() def comm(self, command, addr): """ Implements communication protocol """ + # TODO: add retries on comm errors com_string = str(addr).zfill(3) + command + ';' checksum = self.checksum(com_string) - com_string = '@@@@' + com_string + checksum + com_string = '@@@' + com_string + checksum com_string = com_string.encode('ascii') self.ser.write(com_string) time.sleep(0.1) - reply = self.ser.read(self.ser.inWaiting()) - try: - reply = reply.decode('ascii') - except UnicodeDecodeError: - reply = reply.decode('ascii', 'ignore') - reply = reply.strip('\x00') - reply = '@' + reply - + raw_reply = self.ser.read(self.ser.inWaiting()) + # In case that noise adds zeros on either side: + reply = raw_reply.strip(b'\x00') + reply = reply.decode('ascii') if len(reply) == 0: LOGGER.warning('No such device') - else: - if reply[-3:] == self.checksum(reply[1:-3], reply=True): - reply = reply[6:-3] # Cut away master address and checksum + return '' + # Response structure: '@@@000' + 'ACK' + content + ';' + 2-char-checksum + noise_check_passed = reply.find(';') == len(reply) - 3 + ack = reply[6:9] + content = reply[9:-3] + crc = reply[-2:] + if noise_check_passed: + # Calculate checksum (the first @ is added again during calc) + if crc == self.checksum(reply[1:-2]): + if ack == 'ACK': + return content + elif ack == 'NAK': + message = NAK_TABLE[content] + print('NAK error {} in command: {}'.format(content, message)) + LOGGER.warning( + 'NAK error {} in command: {}'.format(content, message) + ) + return '' + else: + LOGGER.warning('Unexpected response: {}'.format(repr(raw_reply))) + return '' else: LOGGER.error('Checksum error in reply') - reply = '' - if reply[1:4] == 'ACK': - reply = reply[4:-3] - else: - LOGGER.warning('Error in command') - return reply + return '' + else: + LOGGER.warning('Response seemingly too noisy: {}'.format(repr(raw_reply))) + return '' def read_full_scale_range(self, addr): """ Read back the current full scale range from the instrument """ @@ -132,9 +159,14 @@ def read_serial_number(self, addr=254): command = 'SN?' return self.comm(command, addr) + def read_internal_temperature(self, addr=254): + """ Return the internal temperature in degrees Celcius """ + command = 'TA?' + return self.comm(command, addr) + if __name__ == '__main__': MKS = MksGSeries() print(MKS.read_serial_number(1)) print(MKS.read_full_scale_range(1)) -# print(MKS.set_device_address(254,005)) + # print(MKS.set_device_address(254, 005)) From e21e8e1201f646da1a46171918945b89bf4831fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Ejler=20S=C3=B8rensen?= Date: Mon, 6 Nov 2023 08:12:13 +0100 Subject: [PATCH 2/2] drivers/mks_g_series.py: improve readability of comm method --- PyExpLabSys/drivers/mks_g_series.py | 41 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/PyExpLabSys/drivers/mks_g_series.py b/PyExpLabSys/drivers/mks_g_series.py index 418a59b1..4213dad0 100644 --- a/PyExpLabSys/drivers/mks_g_series.py +++ b/PyExpLabSys/drivers/mks_g_series.py @@ -61,33 +61,32 @@ def comm(self, command, addr): if len(reply) == 0: LOGGER.warning('No such device') return '' + # Response structure: '@@@000' + 'ACK' + content + ';' + 2-char-checksum noise_check_passed = reply.find(';') == len(reply) - 3 + if not noise_check_passed: + LOGGER.warning('Response seemingly too noisy: {}'.format(repr(raw_reply))) + return '' + ack = reply[6:9] content = reply[9:-3] - crc = reply[-2:] - if noise_check_passed: - # Calculate checksum (the first @ is added again during calc) - if crc == self.checksum(reply[1:-2]): - if ack == 'ACK': - return content - elif ack == 'NAK': - message = NAK_TABLE[content] - print('NAK error {} in command: {}'.format(content, message)) - LOGGER.warning( - 'NAK error {} in command: {}'.format(content, message) - ) - return '' - else: - LOGGER.warning('Unexpected response: {}'.format(repr(raw_reply))) - return '' - else: - LOGGER.error('Checksum error in reply') - return '' - else: - LOGGER.warning('Response seemingly too noisy: {}'.format(repr(raw_reply))) + # Calculate checksum (the first @ is added again during calc) + if reply[-2:] != self.checksum(reply[1:-2]): + LOGGER.error('Checksum error in reply') return '' + if ack == 'ACK': + return content + + if ack == 'NAK': + message = NAK_TABLE[content] + print('NAK error {} in command: {}'.format(content, message)) + LOGGER.warning('NAK error {} in command: {}'.format(content, message)) + return '' + + LOGGER.warning('Unexpected response: {}'.format(repr(raw_reply))) + return '' + def read_full_scale_range(self, addr): """ Read back the current full scale range from the instrument """ command = 'U?'