Пример #1
0
 def _newbitResponseToValue(self, bytestring):
     minimalmodbus._checkString(bytestring,
                                description='bytestring',
                                minlength=1,
                                maxlength=1)
     returnedValue = ord(bytestring)
     returnedValue &= 0x01
     return returnedValue
Пример #2
0
def _predictResponseSize(mode, functioncode, payloadToSlave):
    """Calculate the number of bytes that should be received from the slave.
    Args:
     * mode (str): The modbus protcol mode (MODE_RTU or MODE_ASCII)
     * functioncode (int): Modbus function code.
     * payloadToSlave (str): The raw request that is to be sent to the slave (not hex encoded string)
    Returns:
        The preducted number of bytes (int) in the response.
    Raises:
        ValueError, TypeError.
    """
    MIN_PAYLOAD_LENGTH = 4  # For implemented functioncodes here
    BYTERANGE_FOR_GIVEN_SIZE = slice(2, 4)  # Within the payload

    NUMBER_OF_PAYLOAD_BYTES_IN_WRITE_CONFIRMATION = 4
    NUMBER_OF_PAYLOAD_BYTES_FOR_BYTECOUNTFIELD = 1

    RTU_TO_ASCII_PAYLOAD_FACTOR = 2

    NUMBER_OF_RTU_RESPONSE_STARTBYTES = 2
    NUMBER_OF_RTU_RESPONSE_ENDBYTES = 2
    NUMBER_OF_ASCII_RESPONSE_STARTBYTES = 5
    NUMBER_OF_ASCII_RESPONSE_ENDBYTES = 4

    # Argument validity testing
    minimalmodbus._checkMode(mode)
    minimalmodbus._checkFunctioncode(functioncode, None)
    minimalmodbus._checkString(payloadToSlave,
                               description='payload',
                               minlength=MIN_PAYLOAD_LENGTH)

    # Calculate payload size
    if functioncode in [5, 6, 15, 16]:
        response_payload_size = NUMBER_OF_PAYLOAD_BYTES_IN_WRITE_CONFIRMATION

    elif functioncode in [1, 2, 3, 4]:
        given_size = minimalmodbus._twoByteStringToNum(
            payloadToSlave[BYTERANGE_FOR_GIVEN_SIZE])
        if functioncode == 1 or functioncode == 2:
            # Algorithm from MODBUS APPLICATION PROTOCOL SPECIFICATION V1.1b
            number_of_inputs = given_size
            response_payload_size = NUMBER_OF_PAYLOAD_BYTES_FOR_BYTECOUNTFIELD + \
                                    number_of_inputs // 8 + (1 if number_of_inputs % 8 else 0)

        elif functioncode == 3 or functioncode == 4:
            number_of_registers = given_size
            response_payload_size = NUMBER_OF_PAYLOAD_BYTES_FOR_BYTECOUNTFIELD + \
                                    number_of_registers * _NUMBER_OF_BYTES_PER_REGISTER

    elif functioncode == 22:
        response_payload_size = 6

    else:
        raise ValueError('Wrong functioncode: {}. The payload is: {!r}'.format( \
            functioncode, payloadToSlave))
    # Calculate number of bytes to read
    if mode == minimalmodbus.MODE_ASCII:
        return NUMBER_OF_ASCII_RESPONSE_STARTBYTES + \
            response_payload_size * RTU_TO_ASCII_PAYLOAD_FACTOR + \
            NUMBER_OF_ASCII_RESPONSE_ENDBYTES
    else:
        return NUMBER_OF_RTU_RESPONSE_STARTBYTES + \
            response_payload_size + \
            NUMBER_OF_RTU_RESPONSE_ENDBYTES
Пример #3
0
    def _genericCommand(self, functioncode, registeraddress, value=None, \
            numberOfDecimals=0, numberOfRegisters=1, signed=False, payloadformat=None):
        """Generic command for reading and writing registers and bits.
        Args:
            * functioncode (int): Modbus function code.
            * registeraddress (int): The register address  (use decimal numbers, not hex).
            * value (numerical or string or None or list of int): The value to store in the register. Depends on payloadformat.
            * numberOfDecimals (int): The number of decimals for content conversion. Only for a single register.
            * numberOfRegisters (int): The number of registers to read/write. Only certain values allowed, depends on payloadformat.
            * signed (bool): Whether the data should be interpreted as unsigned or signed. Only for a single register or for payloadformat='long'.
            * payloadformat (None or string): None, 'long', 'float', 'string', 'register', 'registers'. Not necessary for single registers or bits.
        If a value of 77.0 is stored internally in the slave register as 770,
        then use ``numberOfDecimals=1`` which will divide the received data from the slave by 10
        before returning the value. Similarly ``numberOfDecimals=2`` will divide
        the received data by 100 before returning the value. Same functionality is also used
        when writing data to the slave.
        Returns:
            The register data in numerical value (int or float), or the bit value 0 or 1 (int), or ``None``.
        Raises:
            ValueError, TypeError, IOError
        """
        NUMBER_OF_BITS = 1
        NUMBER_OF_BYTES_FOR_ONE_BIT = 1
        NUMBER_OF_BYTES_BEFORE_REGISTERDATA = 1
        ALL_ALLOWED_FUNCTIONCODES = list(range(
            1, 7)) + [15, 16] + [22]  # To comply with both Python2 and Python3
        MAX_NUMBER_OF_REGISTERS = 255

        # Payload format constants, so datatypes can be told apart.
        # Note that bit datatype not is included, because it uses other functioncodes.
        PAYLOADFORMAT_LONG = 'long'
        PAYLOADFORMAT_FLOAT = 'float'
        PAYLOADFORMAT_STRING = 'string'
        PAYLOADFORMAT_REGISTER = 'register'
        PAYLOADFORMAT_REGISTERS = 'registers'

        ALL_PAYLOADFORMATS = [PAYLOADFORMAT_LONG, PAYLOADFORMAT_FLOAT, \
            PAYLOADFORMAT_STRING, PAYLOADFORMAT_REGISTER, PAYLOADFORMAT_REGISTERS]

        ## Check input values ##
        minimalmodbus._checkFunctioncode(
            functioncode, ALL_ALLOWED_FUNCTIONCODES
        )  # Note: The calling facade functions should validate this
        minimalmodbus._checkRegisteraddress(registeraddress)
        minimalmodbus._checkInt(numberOfDecimals,
                                minvalue=0,
                                description='number of decimals')
        minimalmodbus._checkInt(numberOfRegisters,
                                minvalue=1,
                                maxvalue=MAX_NUMBER_OF_REGISTERS,
                                description='number of registers')
        minimalmodbus._checkBool(signed, description='signed')

        if payloadformat is not None:
            if payloadformat not in ALL_PAYLOADFORMATS:
                raise ValueError(
                    'Wrong payload format variable. Given: {0!r}'.format(
                        payloadformat))

        ## Check combinations of input parameters ##
        numberOfRegisterBytes = numberOfRegisters * _NUMBER_OF_BYTES_PER_REGISTER

        # Payload format
        if functioncode in [3, 4, 6, 16] and payloadformat is None:
            payloadformat = PAYLOADFORMAT_REGISTER

        if functioncode in [3, 4, 6, 16]:
            if payloadformat not in ALL_PAYLOADFORMATS:
                raise ValueError('The payload format is unknown. Given format: {0!r}, functioncode: {1!r}.'.\
                    format(payloadformat, functioncode))
        else:
            if payloadformat is not None:
                raise ValueError('The payload format given is not allowed for this function code. ' + \
                    'Given format: {0!r}, functioncode: {1!r}.'.format(payloadformat, functioncode))

                # Signed and numberOfDecimals
        if signed:
            if payloadformat not in [
                    PAYLOADFORMAT_REGISTER, PAYLOADFORMAT_LONG
            ]:
                raise ValueError('The "signed" parameter can not be used for this data format. ' + \
                    'Given format: {0!r}.'.format(payloadformat))

        if numberOfDecimals > 0 and payloadformat != PAYLOADFORMAT_REGISTER:
            raise ValueError('The "numberOfDecimals" parameter can not be used for this data format. ' + \
                'Given format: {0!r}.'.format(payloadformat))

            # Number of registers
        if functioncode not in [3, 4, 16] and numberOfRegisters != 1:
            raise ValueError('The numberOfRegisters is not valid for this function code. ' + \
                'NumberOfRegisters: {0!r}, functioncode {1}.'.format(numberOfRegisters, functioncode))

        if functioncode == 16 and payloadformat == PAYLOADFORMAT_REGISTER and numberOfRegisters != 1:
            raise ValueError('Wrong numberOfRegisters when writing to a ' + \
                'single register. Given {0!r}.'.format(numberOfRegisters))
            # Note: For function code 16 there is checking also in the content conversion functions.

            # Value
        if functioncode in [5, 6, 15, 16, 22] and value is None:
            raise ValueError('The input value is not valid for this function code. ' + \
                'Given {0!r} and {1}.'.format(value, functioncode))

        if functioncode == 16 and payloadformat in [
                PAYLOADFORMAT_REGISTER, PAYLOADFORMAT_FLOAT, PAYLOADFORMAT_LONG
        ]:
            minimalmodbus._checkNumerical(value, description='input value')

        if functioncode == 6 and payloadformat == PAYLOADFORMAT_REGISTER:
            minimalmodbus._checkNumerical(value, description='input value')

            # Value for string
        if functioncode == 16 and payloadformat == PAYLOADFORMAT_STRING:
            minimalmodbus._checkString(value,
                                       'input string',
                                       minlength=1,
                                       maxlength=numberOfRegisterBytes)
            # Note: The string might be padded later, so the length might be shorter than numberOfRegisterBytes.

            # Value for registers
        if functioncode == 16 and payloadformat == PAYLOADFORMAT_REGISTERS:
            if not isinstance(value, list):
                raise TypeError(
                    'The value parameter must be a list. Given {0!r}.'.format(
                        value))

            if len(value) != numberOfRegisters:
                raise ValueError('The list length does not match number of registers. ' + \
                    'List: {0!r},  Number of registers: {1!r}.'.format(value, numberOfRegisters))

        ## Build payload to slave ##
        if functioncode in [1, 2]:
            payloadToSlave = minimalmodbus._numToTwoByteString(registeraddress) + \
                            minimalmodbus._numToTwoByteString(NUMBER_OF_BITS)

        elif functioncode in [3, 4]:
            payloadToSlave = minimalmodbus._numToTwoByteString(registeraddress) + \
                            minimalmodbus._numToTwoByteString(numberOfRegisters)

        elif functioncode == 5:
            payloadToSlave = minimalmodbus._numToTwoByteString(registeraddress) + \
                            minimalmodbus._createBitpattern(functioncode, value)

        elif functioncode == 6:
            payloadToSlave = minimalmodbus._numToTwoByteString(registeraddress) + \
                            minimalmodbus._numToTwoByteString(value, numberOfDecimals, signed=signed)

        elif functioncode == 15:
            payloadToSlave = minimalmodbus._numToTwoByteString(registeraddress) + \
                            minimalmodbus._numToTwoByteString(NUMBER_OF_BITS) + \
                            minimalmodbus._numToOneByteString(NUMBER_OF_BYTES_FOR_ONE_BIT) + \
                            minimalmodbus._createBitpattern(functioncode, value)

        elif functioncode == 16:
            if payloadformat == PAYLOADFORMAT_REGISTER:
                registerdata = minimalmodbus._numToTwoByteString(
                    value, numberOfDecimals, signed=signed)

            elif payloadformat == PAYLOADFORMAT_STRING:
                registerdata = minimalmodbus._textstringToBytestring(
                    value, numberOfRegisters)

            elif payloadformat == PAYLOADFORMAT_LONG:
                registerdata = minimalmodbus._longToBytestring(
                    value, signed, numberOfRegisters)

            elif payloadformat == PAYLOADFORMAT_FLOAT:
                registerdata = minimalmodbus._floatToBytestring(
                    value, numberOfRegisters)

            elif payloadformat == PAYLOADFORMAT_REGISTERS:
                registerdata = minimalmodbus._valuelistToBytestring(
                    value, numberOfRegisters)

            assert len(registerdata) == numberOfRegisterBytes
            payloadToSlave = minimalmodbus._numToTwoByteString(registeraddress) + \
                            minimalmodbus._numToTwoByteString(numberOfRegisters) + \
                            minimalmodbus._numToOneByteString(numberOfRegisterBytes) + \
                            registerdata
        elif functioncode == 22:
            registerdata = minimalmodbus._valuelistToBytestring(value, 2)
            payloadToSlave = minimalmodbus._numToTwoByteString(registeraddress) + \
                            registerdata
        ## Communicate ##
        payloadFromSlave = self._performCommand(functioncode, payloadToSlave)

        ## Check the contents in the response payload ##
        if functioncode in [1, 2, 3, 4]:
            minimalmodbus._checkResponseByteCount(
                payloadFromSlave)  # response byte count

        if functioncode in [5, 6, 15, 16]:
            minimalmodbus._checkResponseRegisterAddress(
                payloadFromSlave, registeraddress)  # response register address

        if functioncode == 5:
            minimalmodbus._checkResponseWriteData(
                payloadFromSlave,
                minimalmodbus._createBitpattern(functioncode,
                                                value))  # response write data

        if functioncode == 6:
            minimalmodbus._checkResponseWriteData(payloadFromSlave, \
                minimalmodbus._numToTwoByteString(value, numberOfDecimals, signed=signed))  # response write data

        if functioncode == 15:
            minimalmodbus._checkResponseNumberOfRegisters(
                payloadFromSlave, NUMBER_OF_BITS)  # response number of bits

        if functioncode == 16:
            minimalmodbus._checkResponseNumberOfRegisters(
                payloadFromSlave,
                numberOfRegisters)  # response number of registers

        ## Calculate return value ##
        if functioncode in [1, 2]:
            registerdata = payloadFromSlave[
                NUMBER_OF_BYTES_BEFORE_REGISTERDATA:]
            if len(registerdata) != NUMBER_OF_BYTES_FOR_ONE_BIT:
                raise ValueError('The registerdata length does not match NUMBER_OF_BYTES_FOR_ONE_BIT. ' + \
                    'Given {0}.'.format(len(registerdata)))

            return minimalmodbus._bitResponseToValue(registerdata)

        if functioncode in [3, 4]:
            registerdata = payloadFromSlave[
                NUMBER_OF_BYTES_BEFORE_REGISTERDATA:]
            if len(registerdata) != numberOfRegisterBytes:
                raise ValueError('The registerdata length does not match number of register bytes. ' + \
                    'Given {0!r} and {1!r}.'.format(len(registerdata), numberOfRegisterBytes))

            if payloadformat == PAYLOADFORMAT_STRING:
                return minimalmodbus._bytestringToTextstring(
                    registerdata, numberOfRegisters)

            elif payloadformat == PAYLOADFORMAT_LONG:
                return minimalmodbus._bytestringToLong(registerdata, signed,
                                                       numberOfRegisters)

            elif payloadformat == PAYLOADFORMAT_FLOAT:
                return minimalmodbus._bytestringToFloat(
                    registerdata, numberOfRegisters)

            elif payloadformat == PAYLOADFORMAT_REGISTERS:
                return minimalmodbus._bytestringToValuelist(
                    registerdata, numberOfRegisters)

            elif payloadformat == PAYLOADFORMAT_REGISTER:
                return minimalmodbus._twoByteStringToNum(registerdata,
                                                         numberOfDecimals,
                                                         signed=signed)

            raise ValueError('Wrong payloadformat for return value generation. ' + \
                'Given {0}'.format(payloadformat))

        if functioncode == 22:
            registerdata = payloadFromSlave[
                NUMBER_OF_BYTES_BEFORE_REGISTERDATA:]
            return ("OK")
Пример #4
0
    def _communicate(self, request, number_of_bytes_to_read):
        """Talk to the slave via a serial port.
        Args:
            request (str): The raw request that is to be sent to the slave.
            number_of_bytes_to_read (int): number of bytes to read
        Returns:
            The raw data (string) returned from the slave.
        Raises:
            TypeError, ValueError, IOError
        Note that the answer might have strange ASCII control signs, which
        makes it difficult to print it in the promt (messes up a bit).
        Use repr() to make the string printable (shows ASCII values for control signs.)
        Will block until reaching *number_of_bytes_to_read* or timeout.
        If the attribute :attr:`Instrument.debug` is :const:`True`, the communication details are printed.
        If the attribute :attr:`Instrument.close_port_after_each_call` is :const:`True` the
        serial port is closed after each call.
        Timing::
                                                  Request from master (Master is writing)
                                                  |
                                                  |       Response from slave (Master is reading)
                                                  |       |
            ----W----R----------------------------W-------R----------------------------------------
                     |                            |       |
                     |<----- Silent period ------>|       |
                                                  |       |
                             Roundtrip time  ---->|-------|<--
        The resolution for Python's time.time() is lower on Windows than on Linux.
        It is about 16 ms on Windows according to
        http://stackoverflow.com/questions/157359/accurate-timestamping-in-python
        For Python3, the information sent to and from pySerial should be of the type bytes.
        This is taken care of automatically by MinimalModbus.
        
        
        """

        minimalmodbus._checkString(request, minlength=1, description='request')
        minimalmodbus._checkInt(number_of_bytes_to_read)

        if self.debug:
            minimalmodbus._print_out('\nMinimalModbus debug mode. Writing to instrument (expecting {} bytes back): {!r} ({})'. \
                format(number_of_bytes_to_read, request, minimalmodbus._hexlify(request)))

        if self.close_port_after_each_call:
            self.serial.open()

        #self.serial.flushInput() TODO

        if sys.version_info[0] > 2:
            request = bytes(request, encoding='latin1'
                            )  # Convert types to make it Python3 compatible

        # Sleep to make sure 3.5 character times have passed
        minimum_silent_period = minimalmodbus._calculate_minimum_silent_period(
            self.serial.baudrate)
        time_since_read = time.time() - _LATEST_READ_TIMES.get(
            self.serial.port, 0)

        if time_since_read < minimum_silent_period:
            sleep_time = minimum_silent_period - time_since_read

            if self.debug:
                template = 'MinimalModbus debug mode. Sleeping for {:.1f} ms. ' + \
                        'Minimum silent period: {:.1f} ms, time since read: {:.1f} ms.'
                text = template.format(
                    sleep_time * _SECONDS_TO_MILLISECONDS,
                    minimum_silent_period * _SECONDS_TO_MILLISECONDS,
                    time_since_read * _SECONDS_TO_MILLISECONDS)
                minimalmodbus._print_out(text)

            time.sleep(sleep_time)

        elif self.debug:
            template = 'MinimalModbus debug mode. No sleep required before write. ' + \
                'Time since previous read: {:.1f} ms, minimum silent period: {:.2f} ms.'
            text = template.format(
                time_since_read * _SECONDS_TO_MILLISECONDS,
                minimum_silent_period * _SECONDS_TO_MILLISECONDS)
            minimalmodbus._print_out(text)

        # Write request
        latest_write_time = time.time()

        self.serial.write(request)

        # Read and discard local echo
        if self.handle_local_echo:
            localEchoToDiscard = self.serial.read(len(request))
            if self.debug:
                template = 'MinimalModbus debug mode. Discarding this local echo: {!r} ({} bytes).'
                text = template.format(localEchoToDiscard,
                                       len(localEchoToDiscard))
                minimalmodbus._print_out(text)
            if localEchoToDiscard != request:
                template = 'Local echo handling is enabled, but the local echo does not match the sent request. ' + \
                    'Request: {!r} ({} bytes), local echo: {!r} ({} bytes).'
                text = template.format(request, len(request),
                                       localEchoToDiscard,
                                       len(localEchoToDiscard))
                raise IOError(text)

        # Read response
        answer = self.serial.read(number_of_bytes_to_read, request)
        _LATEST_READ_TIMES[self.serial.port] = time.time()

        if self.close_port_after_each_call:
            self.serial.close()

        if sys.version_info[0] > 2:
            answer = str(answer, encoding='latin1'
                         )  # Convert types to make it Python3 compatible

        if self.debug:
            template = 'MinimalModbus debug mode. Response from instrument: {!r} ({}) ({} bytes), ' + \
                'roundtrip time: {:.1f} ms. Timeout setting: {:.1f} ms.\n'
            text = template.format(
                answer, minimalmodbus._hexlify(answer), len(answer),
                (_LATEST_READ_TIMES.get(self.serial.port, 0) -
                 latest_write_time) * _SECONDS_TO_MILLISECONDS,
                self.serial.timeout * _SECONDS_TO_MILLISECONDS)
            minimalmodbus._print_out(text)

        if len(answer) == 0:
            raise IOError('No communication with the instrument (no answer)')

        return answer
Пример #5
0
 def _newbitResponseToValue(self, bytestring):
     minimalmodbus._checkString(bytestring, description='bytestring', minlength=1, maxlength=1)
     returnedValue = ord(bytestring)
     returnedValue &= 0x01
     return returnedValue