Module dopes.equipment_control.mecom.mecom
" The magic happens in this file.
Classes
class ACK-
Expand source code
class ACK(MeFrame): """ ACK command sent by the device. """ _SOURCE = "!" def decompose(self, frame_bytes): """ Takes bytes as input and builds the instance. :param frame_bytes: bytes :return: """ frame_bytes = self._SOURCE.encode() + frame_bytes self._decompose_header(frame_bytes) frame = frame_bytes.decode() self.CRC = int(frame[-4:], 16)ACK command sent by the device.
Ancestors
Methods
def decompose(self, frame_bytes)-
Expand source code
def decompose(self, frame_bytes): """ Takes bytes as input and builds the instance. :param frame_bytes: bytes :return: """ frame_bytes = self._SOURCE.encode() + frame_bytes self._decompose_header(frame_bytes) frame = frame_bytes.decode() self.CRC = int(frame[-4:], 16)Takes bytes as input and builds the instance. :param frame_bytes: bytes :return:
Inherited members
class DeviceError-
Expand source code
class DeviceError(MeFrame): """ Queries failing return a device error, implemented as repsonse by this class. """ _SOURCE = "!" def __init__(self): """ Read error codes from command.py and parse into a list of Error() instances. """ super(DeviceError, self).__init__() self._ERRORS = [] for error in ERRORS: self._ERRORS.append(Error(error)) def _get_by_code(self, code): """ Returns a Error() identified by it's error code. :param code: int :return: Error() """ for error in self._ERRORS: if error.code == code: return error # we do not need to raise here since error are well defined def compose(self, part=False): """ Device errors have a different but simple structure. :param part: bool :return: """ # first part frame = self._SOURCE + "{:02X}".format(self.ADDRESS) + "{:04X}".format(self.SEQUENCE) # payload is ['+', #_of_error] frame += self.PAYLOAD[0] frame += "{:02x}".format(self.PAYLOAD[1]) # if we only want a partial frame, return here if part: return frame.encode() # add checksum if self.CRC is None: self.crc() frame += "{:04X}".format(self.CRC) # add end of line (carriage return) frame += self._EOL return frame.encode() def decompose(self, frame_bytes): """ Again, different but consistent structure. :param frame_bytes: bytes :return: """ frame_bytes = self._SOURCE.encode() + frame_bytes self._decompose_header(frame_bytes) frame = frame_bytes.decode() self.PAYLOAD.append(frame[7]) self.PAYLOAD.append(int(frame[8:10], 16)) self.crc(int(frame[-4:], 16)) def error(self): """ Returns error code, description and symbol as [str,]. :return: [str, str, str] """ error_code = self.PAYLOAD[1] # returns [code, description, symbol] return self._get_by_code(error_code).as_list()Queries failing return a device error, implemented as repsonse by this class.
Read error codes from command.py and parse into a list of Error() instances.
Ancestors
Methods
def compose(self, part=False)-
Expand source code
def compose(self, part=False): """ Device errors have a different but simple structure. :param part: bool :return: """ # first part frame = self._SOURCE + "{:02X}".format(self.ADDRESS) + "{:04X}".format(self.SEQUENCE) # payload is ['+', #_of_error] frame += self.PAYLOAD[0] frame += "{:02x}".format(self.PAYLOAD[1]) # if we only want a partial frame, return here if part: return frame.encode() # add checksum if self.CRC is None: self.crc() frame += "{:04X}".format(self.CRC) # add end of line (carriage return) frame += self._EOL return frame.encode()Device errors have a different but simple structure. :param part: bool :return:
def decompose(self, frame_bytes)-
Expand source code
def decompose(self, frame_bytes): """ Again, different but consistent structure. :param frame_bytes: bytes :return: """ frame_bytes = self._SOURCE.encode() + frame_bytes self._decompose_header(frame_bytes) frame = frame_bytes.decode() self.PAYLOAD.append(frame[7]) self.PAYLOAD.append(int(frame[8:10], 16)) self.crc(int(frame[-4:], 16))Again, different but consistent structure. :param frame_bytes: bytes :return:
def error(self)-
Expand source code
def error(self): """ Returns error code, description and symbol as [str,]. :return: [str, str, str] """ error_code = self.PAYLOAD[1] # returns [code, description, symbol] return self._get_by_code(error_code).as_list()Returns error code, description and symbol as [str,]. :return: [str, str, str]
Inherited members
class Error (error_dict)-
Expand source code
class Error(object): """" Every error dict from commands.py is parsed into a Error instance. """ def __init__(self, error_dict): """ Takes a dict e.g. {"code": 1, "symbol": "EER_CMD_NOT_AVAILABLE", "description": "Command not available"} which defines a error specified by the protocol. :param error_dict: dict """ self.code = error_dict["code"] self.symbol = error_dict["symbol"] self.description = error_dict["description"] def as_list(self): """ Returns a list representation of this object. :return: list """ return [self.code, self.description, self.symbol]" Every error dict from commands.py is parsed into a Error instance.
Takes a dict e.g. {"code": 1, "symbol": "EER_CMD_NOT_AVAILABLE", "description": "Command not available"} which defines a error specified by the protocol. :param error_dict: dict
Methods
def as_list(self)-
Expand source code
def as_list(self): """ Returns a list representation of this object. :return: list """ return [self.code, self.description, self.symbol]Returns a list representation of this object. :return: list
class IF (address=0, parameter_instance=1)-
Expand source code
class IF(Query): """ Implementing device info query. """ _PAYLOAD_START = '?IF' def __init__(self, address=0, parameter_instance=1): """ Create a query to set a parameter value. :param address: int :param parameter_instance: int """ # init header (equal for get and set queries) super(IF, self).__init__(parameter=None, address=address, parameter_instance=parameter_instance) # no need to initialize response format, we want ACKImplementing device info query.
Create a query to set a parameter value. :param address: int :param parameter_instance: int
Ancestors
Inherited members
class IFResponse-
Expand source code
class IFResponse(MeFrame): """ ACK command sent by the device. """ _SOURCE = "!" def crc(self, in_crc=None): """ ACK has the same checksum as the VS command. :param in_crc: int :return: """ pass def decompose(self, frame_bytes): """ Takes bytes as input and builds the instance. :param frame_bytes: bytes :return: """ frame_bytes = self._SOURCE.encode() + frame_bytes self._decompose_header(frame_bytes) frame = frame_bytes.decode() self.PAYLOAD = frame[7:-4] self.CRC = int(frame[-4:], 16)ACK command sent by the device.
Ancestors
Methods
def crc(self, in_crc=None)-
Expand source code
def crc(self, in_crc=None): """ ACK has the same checksum as the VS command. :param in_crc: int :return: """ passACK has the same checksum as the VS command. :param in_crc: int :return:
def decompose(self, frame_bytes)-
Expand source code
def decompose(self, frame_bytes): """ Takes bytes as input and builds the instance. :param frame_bytes: bytes :return: """ frame_bytes = self._SOURCE.encode() + frame_bytes self._decompose_header(frame_bytes) frame = frame_bytes.decode() self.PAYLOAD = frame[7:-4] self.CRC = int(frame[-4:], 16)Takes bytes as input and builds the instance. :param frame_bytes: bytes :return:
Inherited members
class MeCom (serialport='/dev/ttyUSB0', timeout=1, baudrate=57600, metype='TEC')-
Expand source code
class MeCom(MeComSerial): """ Deprecated. Use MeComSerial instead. """ passDeprecated. Use MeComSerial instead.
Initialize communication with serial port. :param serialport: str: Linux example: '/dev/ttyUSB0', Windows example: 'COM1' :param timeout: int :param metype: str: either 'TEC', 'LDD-112x', 'LDD-130x' or 'LDD-1321'
Ancestors
Inherited members
class MeComCommon (metype='TEC')-
Expand source code
class MeComCommon: """ Shared communication class """ SEQUENCE_COUNTER = 1 def __init__(self, metype='TEC'): """ Initialize communication with serial port. :param serialport: str :param timeout: int :param metype: str: either 'TEC', 'LDD-112x', 'LDD-130x' or 'LDD-1321' """ self.lock = Lock() # initialize parameters self.PARAMETERS = ParameterList(metype) def _find_parameter(self, parameter_name, parameter_id): """ Return Parameter() with either name or id given. :param parameter_name: str :param parameter_id: int :return: Parameter """ assert parameter_name is not None or parameter_id is not None return self.PARAMETERS.get_by_name(parameter_name) if parameter_name is not None\ else self.PARAMETERS.get_by_id(parameter_id) def _inc(self): self.SEQUENCE_COUNTER += 1 # sequence in controller is int16 and overflows self.SEQUENCE_COUNTER = self.SEQUENCE_COUNTER % (2**16) @staticmethod def _raise(query): """ If DeviceError is received, raise! :param query: VR or VS :return: """ # did we encounter an error? if type(query.RESPONSE) is DeviceError: code, description, symbol = query.RESPONSE.error() raise ResponseException("device {} raised {}".format(query.RESPONSE.ADDRESS, description)) def _get(self, parameter_name=None, parameter_id=None, *args, **kwargs): """ Get a query object for a VR command. :param parameter_name: :param parameter_id: :param args: :param kwargs: :return: """ # search in DataFrame returns a dict parameter = self._find_parameter(parameter_name, parameter_id) # execute query vr = self._execute(VR(parameter=parameter, *args, **kwargs)) # print(vr.PAYLOAD) # print(vr.RESPONSE.PAYLOAD) # return the query with response return vr def _get_raw(self, parameter_id, parameter_format, *args, **kwargs): """ Get a query object for a VR command (raw version). :param parameter: :param args: :param kwargs: :return: """ # construct from raw id and format specifier parameter = Parameter({"id": parameter_id, "name": None, "format": parameter_format}) # execute query vr = self._execute(VR(parameter=parameter, *args, **kwargs)) # print(vr.PAYLOAD) # print(vr.RESPONSE.PAYLOAD) # return the query with response return vr def _set(self, value, parameter_name=None, parameter_id=None, *args, **kwargs): """ Get a query object for a VS command. :param value: :param parameter_name: :param parameter_id: :param args: :param kwargs: :return: """ # search in DataFrame returns a dict parameter = self._find_parameter(parameter_name, parameter_id) # execute query vs = self._execute(VS(value=value, parameter=parameter, *args, **kwargs)) # return the query with response return vs def _set_raw(self, value, parameter_id, parameter_format, *args, **kwargs): """ Get a query object for a VS command (raw version). :param value: :param parameter: :param args: :param kwargs: :return: """ # construct from raw id and format specifier parameter = Parameter({"id": parameter_id, "name": None, "format": parameter_format}) # execute query vs = self._execute(VS(value=value, parameter=parameter, *args, **kwargs)) # return the query with response return vs def get_parameter(self, parameter_name=None, parameter_id=None, *args, **kwargs): """ Get the value of a parameter given by name or id. Returns a list of success and value. :param parameter_name: :param parameter_id: :param args: :param kwargs: :return: int or float """ # get the query object vr = self._get(parameter_id=parameter_id, parameter_name=parameter_name, *args, **kwargs) return vr.RESPONSE.PAYLOAD[0] def get_parameter_raw(self, parameter_id, parameter_format, *args, **kwargs): """ Get the value of a parameter given by its id and format specifier. note: use get_parameter() if you only want to use known commands Returns a list of success and value. :param parameter: :param args: :param kwargs: :return: int or float """ # get the query object vr = self._get_raw(parameter_id=parameter_id, parameter_format=parameter_format, *args, **kwargs) return vr.RESPONSE.PAYLOAD[0] def set_parameter(self, value, parameter_name=None, parameter_id=None, *args, **kwargs): """ Set the new value of a parameter given by name or id. Returns success. :param value: :param parameter_name: :param parameter_id: :param args: :param kwargs: :return: bool """ # get the query object vs = self._set(value=value, parameter_id=parameter_id, parameter_name=parameter_name, *args, **kwargs) # check if value setting has succeeded # # Not necessary as we get an acknowledge response or Value is out of range # exception when an invalid value was passed. # current implementation also often fails due to rounding, e.g. setting 1.0 # but returning 0.999755859375 when performing a self.get_parameter # value_set = self.get_parameter(parameter_id=parameter_id, parameter_name=parameter_name, *args, **kwargs) # return True if we got an ACK return type(vs.RESPONSE) == ACK def set_parameter_raw(self, value, parameter_id, parameter_format, *args, **kwargs): """ Set the new value of a parameter given by its id and format specifier. note: use set_parameter() if you only want to use known commands Returns success. :param value: :param parameter: :param args: :param kwargs: :return: bool """ # get the query object vs = self._set_raw(value=value, parameter_id=parameter_id, parameter_format=parameter_format, *args, **kwargs) # check if value setting has succeeded # # Not necessary as we get an acknowledge response or Value is out of range # exception when an invalid value was passed. # current implementation also often fails due to rounding, e.g. setting 1.0 # but returning 0.999755859375 when performing a self.get_parameter # value_set = self.get_parameter(parameter_id=parameter_id, parameter_name=parameter_name, *args, **kwargs) # return True if we got an ACK return type(vs.RESPONSE) == ACK def reset_device(self,*args, **kwargs): """ Resets the device after an error has occured """ rs = self._execute(RS(*args, **kwargs)) return type(rs.RESPONSE) == ACK def info(self,*args, **kwargs): """ Resets the device after an error has occured """ info = self._execute(IF(*args, **kwargs)) return info.RESPONSE.PAYLOAD # returns device address identify = partialmethod(get_parameter, parameter_name="Device Address") """ Returns success and device address as int. """ def status(self, *args, **kwargs): """ Get the device status. Returns success and status as readable str. :param args: :param kwargs: :return: [bool, str] """ # query device status status_id = self.get_parameter(parameter_name="Device Status", *args, **kwargs) if status_id == 0: status_name = "Init" elif status_id == 1: status_name = "Ready" elif status_id == 2: status_name = "Run" elif status_id == 3: status_name = "Error" elif status_id == 4: status_name = "Bootloader" elif status_id == 5: status_name = "Device will Reset within next 200ms" else: status_name = "Unknown" # return address and status return status_name # enable or disable auto saving to flash enable_autosave = partialmethod(set_parameter, value=0, parameter_name="Save Data to Flash") disable_autosave = partialmethod(set_parameter, value=1, parameter_name="Save Data to Flash") def write_to_flash(self, *args, **kwargs): """ Write parameters to flash. Note: This function only works on: - TEC Controllers using a firmware < v6.00 - LDD-130x devices with a firmware < v2.00 - LDD-112x devices on any firmware for devices not listed here, refer to the device documentation and check whether the old automatic flash saving mechanism is used on your device :param args: :param kwargs: :return: bool """ self.enable_autosave() timer_start = time.time() # value 0 means "All Parameters are saved to Flash" while self.get_parameter(parameter_name="Flash Status") != 0: # check for timeout if time.time() - timer_start > 10: raise ResponseTimeout("writing to flash timed out!") time.sleep(0.5) self.disable_autosave() return True def trigger_save_to_flash(self, *args, **kwargs): """ Writes all parameter values to the flash of the device. Note: This function only works on: - TEC Controllers using a firmware >= v6.00 - LDD-130x devices with a firmware >= v2.00 - LDD-1321 devices on any firmware for devices not listed here, refer to the device documentation and check whether the new explicit flash saving mechanism is used on your device """ rs = self._execute(SP(*args, **kwargs)) return type(rs.RESPONSE) == ACKShared communication class
Initialize communication with serial port. :param serialport: str :param timeout: int :param metype: str: either 'TEC', 'LDD-112x', 'LDD-130x' or 'LDD-1321'
Subclasses
Class variables
var SEQUENCE_COUNTER
Methods
def disable_autosave(self, *, value=1, parameter_name='Save Data to Flash', parameter_id=None, **kwargs)-
Expand source code
def _method(cls_or_self, /, *args, **keywords): keywords = {**self.keywords, **keywords} return self.func(cls_or_self, *self.args, *args, **keywords) def enable_autosave(self, *, value=0, parameter_name='Save Data to Flash', parameter_id=None, **kwargs)-
Expand source code
def _method(cls_or_self, /, *args, **keywords): keywords = {**self.keywords, **keywords} return self.func(cls_or_self, *self.args, *args, **keywords) def get_parameter(self, parameter_name=None, parameter_id=None, *args, **kwargs)-
Expand source code
def get_parameter(self, parameter_name=None, parameter_id=None, *args, **kwargs): """ Get the value of a parameter given by name or id. Returns a list of success and value. :param parameter_name: :param parameter_id: :param args: :param kwargs: :return: int or float """ # get the query object vr = self._get(parameter_id=parameter_id, parameter_name=parameter_name, *args, **kwargs) return vr.RESPONSE.PAYLOAD[0]Get the value of a parameter given by name or id. Returns a list of success and value. :param parameter_name: :param parameter_id: :param args: :param kwargs: :return: int or float
def get_parameter_raw(self, parameter_id, parameter_format, *args, **kwargs)-
Expand source code
def get_parameter_raw(self, parameter_id, parameter_format, *args, **kwargs): """ Get the value of a parameter given by its id and format specifier. note: use get_parameter() if you only want to use known commands Returns a list of success and value. :param parameter: :param args: :param kwargs: :return: int or float """ # get the query object vr = self._get_raw(parameter_id=parameter_id, parameter_format=parameter_format, *args, **kwargs) return vr.RESPONSE.PAYLOAD[0]Get the value of a parameter given by its id and format specifier. note: use get_parameter() if you only want to use known commands Returns a list of success and value. :param parameter: :param args: :param kwargs: :return: int or float
def identify(self, *, parameter_name='Device Address', parameter_id=None, **kwargs)-
Expand source code
def _method(cls_or_self, /, *args, **keywords): keywords = {**self.keywords, **keywords} return self.func(cls_or_self, *self.args, *args, **keywords) def info(self, *args, **kwargs)-
Expand source code
def info(self,*args, **kwargs): """ Resets the device after an error has occured """ info = self._execute(IF(*args, **kwargs)) return info.RESPONSE.PAYLOADResets the device after an error has occured
def reset_device(self, *args, **kwargs)-
Expand source code
def reset_device(self,*args, **kwargs): """ Resets the device after an error has occured """ rs = self._execute(RS(*args, **kwargs)) return type(rs.RESPONSE) == ACKResets the device after an error has occured
def set_parameter(self, value, parameter_name=None, parameter_id=None, *args, **kwargs)-
Expand source code
def set_parameter(self, value, parameter_name=None, parameter_id=None, *args, **kwargs): """ Set the new value of a parameter given by name or id. Returns success. :param value: :param parameter_name: :param parameter_id: :param args: :param kwargs: :return: bool """ # get the query object vs = self._set(value=value, parameter_id=parameter_id, parameter_name=parameter_name, *args, **kwargs) # check if value setting has succeeded # # Not necessary as we get an acknowledge response or Value is out of range # exception when an invalid value was passed. # current implementation also often fails due to rounding, e.g. setting 1.0 # but returning 0.999755859375 when performing a self.get_parameter # value_set = self.get_parameter(parameter_id=parameter_id, parameter_name=parameter_name, *args, **kwargs) # return True if we got an ACK return type(vs.RESPONSE) == ACKSet the new value of a parameter given by name or id. Returns success. :param value: :param parameter_name: :param parameter_id: :param args: :param kwargs: :return: bool
def set_parameter_raw(self, value, parameter_id, parameter_format, *args, **kwargs)-
Expand source code
def set_parameter_raw(self, value, parameter_id, parameter_format, *args, **kwargs): """ Set the new value of a parameter given by its id and format specifier. note: use set_parameter() if you only want to use known commands Returns success. :param value: :param parameter: :param args: :param kwargs: :return: bool """ # get the query object vs = self._set_raw(value=value, parameter_id=parameter_id, parameter_format=parameter_format, *args, **kwargs) # check if value setting has succeeded # # Not necessary as we get an acknowledge response or Value is out of range # exception when an invalid value was passed. # current implementation also often fails due to rounding, e.g. setting 1.0 # but returning 0.999755859375 when performing a self.get_parameter # value_set = self.get_parameter(parameter_id=parameter_id, parameter_name=parameter_name, *args, **kwargs) # return True if we got an ACK return type(vs.RESPONSE) == ACKSet the new value of a parameter given by its id and format specifier. note: use set_parameter() if you only want to use known commands Returns success. :param value: :param parameter: :param args: :param kwargs: :return: bool
def status(self, *args, **kwargs)-
Expand source code
def status(self, *args, **kwargs): """ Get the device status. Returns success and status as readable str. :param args: :param kwargs: :return: [bool, str] """ # query device status status_id = self.get_parameter(parameter_name="Device Status", *args, **kwargs) if status_id == 0: status_name = "Init" elif status_id == 1: status_name = "Ready" elif status_id == 2: status_name = "Run" elif status_id == 3: status_name = "Error" elif status_id == 4: status_name = "Bootloader" elif status_id == 5: status_name = "Device will Reset within next 200ms" else: status_name = "Unknown" # return address and status return status_nameGet the device status. Returns success and status as readable str. :param args: :param kwargs: :return: [bool, str]
def trigger_save_to_flash(self, *args, **kwargs)-
Expand source code
def trigger_save_to_flash(self, *args, **kwargs): """ Writes all parameter values to the flash of the device. Note: This function only works on: - TEC Controllers using a firmware >= v6.00 - LDD-130x devices with a firmware >= v2.00 - LDD-1321 devices on any firmware for devices not listed here, refer to the device documentation and check whether the new explicit flash saving mechanism is used on your device """ rs = self._execute(SP(*args, **kwargs)) return type(rs.RESPONSE) == ACKWrites all parameter values to the flash of the device. Note: This function only works on: - TEC Controllers using a firmware >= v6.00 - LDD-130x devices with a firmware >= v2.00 - LDD-1321 devices on any firmware for devices not listed here, refer to the device documentation and check whether the new explicit flash saving mechanism is used on your device
def write_to_flash(self, *args, **kwargs)-
Expand source code
def write_to_flash(self, *args, **kwargs): """ Write parameters to flash. Note: This function only works on: - TEC Controllers using a firmware < v6.00 - LDD-130x devices with a firmware < v2.00 - LDD-112x devices on any firmware for devices not listed here, refer to the device documentation and check whether the old automatic flash saving mechanism is used on your device :param args: :param kwargs: :return: bool """ self.enable_autosave() timer_start = time.time() # value 0 means "All Parameters are saved to Flash" while self.get_parameter(parameter_name="Flash Status") != 0: # check for timeout if time.time() - timer_start > 10: raise ResponseTimeout("writing to flash timed out!") time.sleep(0.5) self.disable_autosave() return TrueWrite parameters to flash. Note: This function only works on: - TEC Controllers using a firmware < v6.00 - LDD-130x devices with a firmware < v2.00 - LDD-112x devices on any firmware for devices not listed here, refer to the device documentation and check whether the old automatic flash saving mechanism is used on your device :param args: :param kwargs: :return: bool
class MeComSerial (serialport='/dev/ttyUSB0', timeout=1, baudrate=57600, metype='TEC')-
Expand source code
class MeComSerial(MeComCommon): """ Main class (Serial). Import this one: from qao.devices.mecom import MeComSerial For a usage example see __main__ """ SEQUENCE_COUNTER = 1 def __init__(self, serialport="/dev/ttyUSB0", timeout=1, baudrate=57600, metype='TEC'): """ Initialize communication with serial port. :param serialport: str: Linux example: '/dev/ttyUSB0', Windows example: 'COM1' :param timeout: int :param metype: str: either 'TEC', 'LDD-112x', 'LDD-130x' or 'LDD-1321' """ # initialize serial connection self.ser = Serial(port=serialport, timeout=timeout, write_timeout=timeout, baudrate=baudrate) # start protocol thread # self.protocol = ReaderThread(serial_instance=self.ser, protocol_factory=MePacket) # self.receiver = self.protocol.__enter__() # initialize parameters self.PARAMETERS = ParameterList(metype) super().__init__(metype) def __exit__(self, exc_type, exc_val, exc_tb): self.ser.__exit__(exc_type, exc_val, exc_tb) def __enter__(self): return self def stop(self): self.ser.flush() self.ser.close() def _read(self, size): """ Read n=size bytes from serial, if <n bytes are received (serial.read() return because of timeout), raise a timeout. """ recv = self.ser.read(size=size) if len(recv) < size: raise ResponseTimeout("timeout while communication via serial") else: return recv def _execute(self, query): self.lock.acquire() try: # clear buffers self.ser.reset_output_buffer() self.ser.reset_input_buffer() query.set_sequence(self.SEQUENCE_COUNTER) # send query self.ser.write(query.compose()) # print(query.compose()) # flush write cache self.ser.flush() # initialize response and carriage return cr = "\r".encode() response_frame = b'' response_byte = self._read(size=1) # read one byte at a time, timeout is set on instance level # read until stop byte while response_byte != cr: response_frame += response_byte response_byte = self._read(size=1) finally: # increment sequence counter self._inc() self.lock.release() # strip source byte (! or #, but for a response always !) response_frame = response_frame[1:] # print(response_frame) query.set_response(response_frame) # did we encounter an error? self._raise(query) return queryMain class (Serial). Import this one: from qao.devices.mecom import MeComSerial
For a usage example see main
Initialize communication with serial port. :param serialport: str: Linux example: '/dev/ttyUSB0', Windows example: 'COM1' :param timeout: int :param metype: str: either 'TEC', 'LDD-112x', 'LDD-130x' or 'LDD-1321'
Ancestors
Subclasses
Class variables
var SEQUENCE_COUNTER
Methods
def stop(self)-
Expand source code
def stop(self): self.ser.flush() self.ser.close()
Inherited members
class MeComTcp (ipaddress, ipport=50000, metype='TEC')-
Expand source code
class MeComTcp(MeComCommon): """ Main class (TCP). Import this one: from qao.devices.mecom import MeComTCP For a usage example see __main__ """ SEQUENCE_COUNTER = 1 def __init__(self, ipaddress, ipport=50000, metype='TEC'): """ Initialize communication with TCP connection. :param ipaddress: str :param ipport: int :param timeout: int :param metype: str: either 'TEC', 'LDD-112x', 'LDD-130x' or 'LDD-1321' """ # initialize network connection self.tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.tcp.connect((ipaddress, ipport)) # initialize parameters self.PARAMETERS = ParameterList(metype) super().__init__(metype) def __exit__(self, exc_type, exc_val, exc_tb): self.tcp.__exit__(exc_type, exc_val, exc_tb) def __enter__(self): return self def stop(self): self.tcp.close() def _read(self, size): """ Read n=size bytes from TCP, if <n bytes are received, raise a timeout. """ recv = b"" while (size - len(recv)) > 0: recv += self.tcp.recv(size - len(recv)) if len(recv) < size: raise ResponseTimeout("timeout while communication via network") else: return recv def _execute(self, query): self.lock.acquire() try: query.set_sequence(self.SEQUENCE_COUNTER) # send query self.tcp.sendall(query.compose()) # print(query.compose()) # initialize response and carriage return cr = "\r".encode() response_frame = b'' response_byte = self._read(size=1) # read one byte at a time, timeout is set on instance level # read until stop byte while response_byte != cr: response_frame += response_byte response_byte = self._read(size=1) finally: # increment sequence counter self._inc() self.lock.release() # strip source byte (! or #, but for a response always !) response_frame = response_frame[1:] # print(response_frame) query.set_response(response_frame) # did we encounter an error? self._raise(query) return queryMain class (TCP). Import this one: from qao.devices.mecom import MeComTCP
For a usage example see main
Initialize communication with TCP connection. :param ipaddress: str :param ipport: int :param timeout: int :param metype: str: either 'TEC', 'LDD-112x', 'LDD-130x' or 'LDD-1321'
Ancestors
Class variables
var SEQUENCE_COUNTER
Methods
def stop(self)-
Expand source code
def stop(self): self.tcp.close()
Inherited members
class MeFrame-
Expand source code
class MeFrame(object): """ Basis structure of a MeCom frame as defined in the specs. """ _TYPES = {"UINT8": "!H", "UINT16": "!L", "INT32": "!i", "FLOAT32": "!f"} _SOURCE = "" _EOL = "\r" # carriage return def __init__(self): self.ADDRESS = 0 self.SEQUENCE = 0 self.PAYLOAD = [] self.CRC = None def CalcCRC_CCITT(self, input_data): """ Calculates the CRC-CCITT checksum of the given data """ CRC = 0 for byte in input_data: CRC ^= byte << 8 for _ in range(8): if (CRC & 0x8000) != 0: CRC = (CRC << 1) ^ 0x1021 # CCITT CRC-16 Polynomial else: CRC = CRC << 1 CRC &= 0xFFFF return CRC def crc(self, in_crc=None): """ Calculates the checksum of a given frame, if a checksum is given as parameter, the two are compared. :param in_crc: :return: int """ if self.CRC is None: self.CRC = self.CalcCRC_CCITT(input_data=self.compose(part=True)) # crc check # print(self.CRC) # print(in_crc) if in_crc is not None and in_crc != self.CRC: raise WrongChecksum def set_sequence(self, sequence): self.SEQUENCE = sequence def compose(self, part=False): """ Returns the frame as bytes, the return-value can be directly send via serial. :param part: bool :return: bytes """ # first part frame = self._SOURCE + "{:02X}".format(self.ADDRESS) + "{:04X}".format(self.SEQUENCE) # payload can be str or float or int for p in self.PAYLOAD: if type(p) is str: frame += p elif type(p) is int: frame += "{:08X}".format(p) elif type(p) is float: # frame += hex(unpack('<I', pack('<f', p))[0])[2:].upper() # please do not ask # if p = 0 CRC fails, e.g. !01000400000000 composes to b'!0100040' / missing zero padding frame += '{:08X}'.format(unpack('<I', pack('<f', p))[0]) #still do not aks # if we only want a partial frame, return here if part: return frame.encode() # add checksum if self.CRC is None: self.crc() frame += "{:04X}".format(self.CRC) # add end of line (carriage return) frame += self._EOL return frame.encode() def _decompose_header(self, frame_bytes): """ Takes bytes as input and decomposes into the instance variables. :param frame_bytes: bytes :return: """ frame = frame_bytes.decode() self._SOURCE = frame[0] self.ADDRESS = int(frame[1:3], 16) self.SEQUENCE = int(frame[3:7], 16)Basis structure of a MeCom frame as defined in the specs.
Subclasses
Methods
def CalcCRC_CCITT(self, input_data)-
Expand source code
def CalcCRC_CCITT(self, input_data): """ Calculates the CRC-CCITT checksum of the given data """ CRC = 0 for byte in input_data: CRC ^= byte << 8 for _ in range(8): if (CRC & 0x8000) != 0: CRC = (CRC << 1) ^ 0x1021 # CCITT CRC-16 Polynomial else: CRC = CRC << 1 CRC &= 0xFFFF return CRCCalculates the CRC-CCITT checksum of the given data
def compose(self, part=False)-
Expand source code
def compose(self, part=False): """ Returns the frame as bytes, the return-value can be directly send via serial. :param part: bool :return: bytes """ # first part frame = self._SOURCE + "{:02X}".format(self.ADDRESS) + "{:04X}".format(self.SEQUENCE) # payload can be str or float or int for p in self.PAYLOAD: if type(p) is str: frame += p elif type(p) is int: frame += "{:08X}".format(p) elif type(p) is float: # frame += hex(unpack('<I', pack('<f', p))[0])[2:].upper() # please do not ask # if p = 0 CRC fails, e.g. !01000400000000 composes to b'!0100040' / missing zero padding frame += '{:08X}'.format(unpack('<I', pack('<f', p))[0]) #still do not aks # if we only want a partial frame, return here if part: return frame.encode() # add checksum if self.CRC is None: self.crc() frame += "{:04X}".format(self.CRC) # add end of line (carriage return) frame += self._EOL return frame.encode()Returns the frame as bytes, the return-value can be directly send via serial. :param part: bool :return: bytes
def crc(self, in_crc=None)-
Expand source code
def crc(self, in_crc=None): """ Calculates the checksum of a given frame, if a checksum is given as parameter, the two are compared. :param in_crc: :return: int """ if self.CRC is None: self.CRC = self.CalcCRC_CCITT(input_data=self.compose(part=True)) # crc check # print(self.CRC) # print(in_crc) if in_crc is not None and in_crc != self.CRC: raise WrongChecksumCalculates the checksum of a given frame, if a checksum is given as parameter, the two are compared. :param in_crc: :return: int
def set_sequence(self, sequence)-
Expand source code
def set_sequence(self, sequence): self.SEQUENCE = sequence
class Parameter (parameter_dict)-
Expand source code
class Parameter(object): """" Every parameter dict from commands.py is parsed into a Parameter instance. """ def __init__(self, parameter_dict): """ Takes a dict e.g. {"id": 104, "name": "Device Status", "format": "INT32"} and creates an object which can be passed to a Query(). :param parameter_dict: dict """ self.id = parameter_dict["id"] self.name = parameter_dict["name"] self.format = parameter_dict["format"]" Every parameter dict from commands.py is parsed into a Parameter instance.
Takes a dict e.g. {"id": 104, "name": "Device Status", "format": "INT32"} and creates an object which can be passed to a Query(). :param parameter_dict: dict
class ParameterList (metype='TEC')-
Expand source code
class ParameterList(object): """ Contains a list of Parameter() for either TEC controller (metype = 'TEC'), LDD-112x (metype = 'LDD-112x'), LDD-130x (metype = 'LDD-130x') or LDD-1321 (metype = 'LDD-1321'). Provides searching via id or name. The deprecated metype = 'LDD' is equal to passing metype = 'LDD-112x'. :param error_dict: dict """ def __init__(self,metype='TEC'): """ Reads the parameter dicts from commands.py. """ self._PARAMETERS = [] if metype == 'TEC': for parameter in TEC_PARAMETERS: self._PARAMETERS.append(Parameter(parameter)) elif metype =='LDD-112x' or metype =='LDD': for parameter in LDD_112x_PARAMETERS: self._PARAMETERS.append(Parameter(parameter)) elif metype =='LDD-130x': for parameter in LDD_130x_PARAMETERS: self._PARAMETERS.append(Parameter(parameter)) elif metype =='LDD-1321': for parameter in LDD_1321_PARAMETERS: self._PARAMETERS.append(Parameter(parameter)) else: raise UnknownMeComType def get_by_id(self, id): """ Returns a Parameter() identified by it's id. :param id: int :return: Parameter() """ for parameter in self._PARAMETERS: if parameter.id == id: return parameter raise UnknownParameter def get_by_name(self, name): """ Returns a Parameter() identified by it's name. :param name: str :return: Parameter() """ for parameter in self._PARAMETERS: if parameter.name == name: return parameter raise UnknownParameterContains a list of Parameter() for either TEC controller (metype = 'TEC'), LDD-112x (metype = 'LDD-112x'), LDD-130x (metype = 'LDD-130x') or LDD-1321 (metype = 'LDD-1321'). Provides searching via id or name. The deprecated metype = 'LDD' is equal to passing metype = 'LDD-112x'. :param error_dict: dict
Reads the parameter dicts from commands.py.
Methods
def get_by_id(self, id)-
Expand source code
def get_by_id(self, id): """ Returns a Parameter() identified by it's id. :param id: int :return: Parameter() """ for parameter in self._PARAMETERS: if parameter.id == id: return parameter raise UnknownParameterReturns a Parameter() identified by it's id. :param id: int :return: Parameter()
def get_by_name(self, name)-
Expand source code
def get_by_name(self, name): """ Returns a Parameter() identified by it's name. :param name: str :return: Parameter() """ for parameter in self._PARAMETERS: if parameter.name == name: return parameter raise UnknownParameterReturns a Parameter() identified by it's name. :param name: str :return: Parameter()
class Query (parameter=None, address=0, parameter_instance=1)-
Expand source code
class Query(MeFrame): """ Basic structure of a query to get or set a parameter. Has the attribute RESPONSE which contains the answer received by the device. The response is set via set_response """ _SOURCE = "#" _PAYLOAD_START = None def __init__(self, parameter=None, address=0, parameter_instance=1): """ To be initialized with a target device address (default=broadcast), the channel, teh sequence number and a Parameter() instance of the corresponding parameter. :param parameter: Parameter :param sequence: int :param address: int :param parameter_instance: int """ super(Query, self).__init__() if hasattr(self, "_PAYLOAD_START"): self.PAYLOAD.append(self._PAYLOAD_START) self.RESPONSE = None self._RESPONSE_FORMAT = None self.ADDRESS = address if parameter is not None: # UNIT16 4 hex digits self.PAYLOAD.append("{:04X}".format(parameter.id)) # UNIT8 2 hex digits self.PAYLOAD.append("{:02X}".format(parameter_instance)) def set_response(self, response_frame): """ Takes the bytes received from the device as input and creates the corresponding response instance. :param response_frame: bytes :return: """ # check the type of the response # is it an ACK packet? if len(response_frame) == 10: self.RESPONSE = ACK() self.RESPONSE.decompose(response_frame) # is it an info string packet/response_frame does not contain source (!) elif len(response_frame) == 30: self.RESPONSE = IFResponse() self.RESPONSE.decompose(response_frame) # is it an error packet? elif b'+' in response_frame: self.RESPONSE = DeviceError() self.RESPONSE.decompose(response_frame) # nope it's a response to a parameter query else: self.RESPONSE = VRResponse(self._RESPONSE_FORMAT) # if the checksum is wrong, this statement raises self.RESPONSE.decompose(response_frame) # did we get the right response to our query? if self.SEQUENCE != self.RESPONSE.SEQUENCE: raise WrongResponseSequenceBasic structure of a query to get or set a parameter. Has the attribute RESPONSE which contains the answer received by the device. The response is set via set_response
To be initialized with a target device address (default=broadcast), the channel, teh sequence number and a Parameter() instance of the corresponding parameter. :param parameter: Parameter :param sequence: int :param address: int :param parameter_instance: int
Ancestors
Subclasses
Methods
def set_response(self, response_frame)-
Expand source code
def set_response(self, response_frame): """ Takes the bytes received from the device as input and creates the corresponding response instance. :param response_frame: bytes :return: """ # check the type of the response # is it an ACK packet? if len(response_frame) == 10: self.RESPONSE = ACK() self.RESPONSE.decompose(response_frame) # is it an info string packet/response_frame does not contain source (!) elif len(response_frame) == 30: self.RESPONSE = IFResponse() self.RESPONSE.decompose(response_frame) # is it an error packet? elif b'+' in response_frame: self.RESPONSE = DeviceError() self.RESPONSE.decompose(response_frame) # nope it's a response to a parameter query else: self.RESPONSE = VRResponse(self._RESPONSE_FORMAT) # if the checksum is wrong, this statement raises self.RESPONSE.decompose(response_frame) # did we get the right response to our query? if self.SEQUENCE != self.RESPONSE.SEQUENCE: raise WrongResponseSequenceTakes the bytes received from the device as input and creates the corresponding response instance. :param response_frame: bytes :return:
Inherited members
class RS (address=0)-
Expand source code
class RS(Query): """ Implementing system reset. """ _PAYLOAD_START = 'RS' def __init__(self, address=0): """ Create a query to set a parameter value. :param address: int :param parameter_instance: int """ # init header super(RS, self).__init__(parameter=None, address=address) # no need to initialize response format, we want ACKImplementing system reset.
Create a query to set a parameter value. :param address: int :param parameter_instance: int
Ancestors
Inherited members
class SP (address=0)-
Expand source code
class SP(Query): """ Implementing query to save all parameter values to flash. """ _PAYLOAD_START = 'SP' def __init__(self, address=0): """ Create a query to set a parameter value. :param address: int """ # init header super(SP, self).__init__(parameter=None, address=address) # no need to initialize response format, we want ACKImplementing query to save all parameter values to flash.
Create a query to set a parameter value. :param address: int
Ancestors
Inherited members
class VR (parameter, address=0, parameter_instance=1)-
Expand source code
class VR(Query): """ Implementing query to get a parameter from the device (?VR). """ _PAYLOAD_START = "?VR" def __init__(self, parameter, address=0, parameter_instance=1): """ Create a query to get a parameter value. :param parameter: Parameter :param address: int :param parameter_instance: int """ # init header (equal for get and set queries super(VR, self).__init__(parameter=parameter, address=address, parameter_instance=parameter_instance) # initialize response assert parameter.format in self._TYPES.keys() self._RESPONSE_FORMAT = parameter.formatImplementing query to get a parameter from the device (?VR).
Create a query to get a parameter value. :param parameter: Parameter :param address: int :param parameter_instance: int
Ancestors
Inherited members
class VRResponse (response_format)-
Expand source code
class VRResponse(MeFrame): """ Frame for the device response to a VR() query. """ _SOURCE = "!" _RESPONSE_FORMAT = None def __init__(self, response_format): """ The format of the response is given via VR.set_response() :param response_format: str """ super(VRResponse, self).__init__() self._RESPONSE_FORMAT = self._TYPES[response_format] def decompose(self, frame_bytes): """ Takes bytes as input and builds the instance. :param frame_bytes: bytes :return: """ assert self._RESPONSE_FORMAT is not None frame_bytes = self._SOURCE.encode() + frame_bytes self._decompose_header(frame_bytes) frame = frame_bytes.decode() self.PAYLOAD = [unpack(self._RESPONSE_FORMAT, bytes.fromhex(frame[7:15]))[0]] # convert hex to float or int self.crc(int(frame[-4:], 16)) # sets crc or raisesFrame for the device response to a VR() query.
The format of the response is given via VR.set_response() :param response_format: str
Ancestors
Methods
def decompose(self, frame_bytes)-
Expand source code
def decompose(self, frame_bytes): """ Takes bytes as input and builds the instance. :param frame_bytes: bytes :return: """ assert self._RESPONSE_FORMAT is not None frame_bytes = self._SOURCE.encode() + frame_bytes self._decompose_header(frame_bytes) frame = frame_bytes.decode() self.PAYLOAD = [unpack(self._RESPONSE_FORMAT, bytes.fromhex(frame[7:15]))[0]] # convert hex to float or int self.crc(int(frame[-4:], 16)) # sets crc or raisesTakes bytes as input and builds the instance. :param frame_bytes: bytes :return:
Inherited members
class VS (value, parameter, address=0, parameter_instance=1)-
Expand source code
class VS(Query): """ Implementing query to set a parameter from the device (VS). """ _PAYLOAD_START = "VS" def __init__(self, value, parameter, address=0, parameter_instance=1): """ Create a query to set a parameter value. :param value: int or float :param parameter: Parameter :param address: int :param parameter_instance: int """ # init header (equal for get and set queries) super(VS, self).__init__(parameter=parameter, address=address, parameter_instance=parameter_instance) # cast the value parameter to the correct type conversions = {'FLOAT32': float, 'INT32': int} assert parameter.format in conversions.keys() value=conversions[parameter.format](value) # the set value self.PAYLOAD.append(value) # no need to initialize response format, we want ACKImplementing query to set a parameter from the device (VS).
Create a query to set a parameter value. :param value: int or float :param parameter: Parameter :param address: int :param parameter_instance: int
Ancestors
Inherited members