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 ACK

Implementing 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:
    """
    pass

ACK 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.
    """
    pass

Deprecated. 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) == ACK

Shared 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.PAYLOAD

Resets 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) == ACK

Resets 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) == ACK

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

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) == ACK

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

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_name

Get 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) == ACK

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

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 True

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

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 query

Main 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 query

Main 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 CRC

Calculates 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 WrongChecksum

Calculates 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 UnknownParameter

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

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 UnknownParameter

Returns 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 UnknownParameter

Returns 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 WrongResponseSequence

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

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 WrongResponseSequence

Takes 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 ACK

Implementing 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 ACK

Implementing 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.format

Implementing 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 raises

Frame 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 raises

Takes 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 ACK

Implementing 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