Esempio n. 1
0
    def setValues(self, address, values, use_as_default=False):
        ''' Sets the requested values of the datastore

        :param address: The starting address
        :param values: The new values to be set
        :param use_as_default: Use the values as default
        '''
        if isinstance(values, dict):
            new_offsets = list(
                set(list(values.keys())) - set(list(self.values.keys())))
            if new_offsets and not self.mutable:
                raise ParameterException("Offsets {} not "
                                         "in range".format(new_offsets))
            self._process_values(values)
        else:
            if not isinstance(values, list):
                values = [values]
            for idx, val in enumerate(values):
                if address + idx not in self.values and not self.mutable:
                    raise ParameterException("Offset {} not "
                                             "in range".format(address + idx))
                self.values[address + idx] = val
        if not self.address:
            self.address = get_next(iterkeys(self.values), None)
        if use_as_default:
            for idx, val in iteritems(self.values):
                self.default_value[idx] = val
Esempio n. 2
0
    def decode(self, event):
        ''' Decodes the event message to its status bits

        :param event: The event to decode
        '''
        if event != self.__encoded:
            raise ParameterException('Invalid decoded value')
Esempio n. 3
0
    def validate(self, slave, fx, address, count=1):
        ''' Checks to see if the request is in range

        :param address: The starting address
        :param count: The number of values to test for

        :returns: True if the request is within range, False otherwise
        '''
        logging.debug("In CbModbusDatastore.validate")

        if fx == ModbusFunctionCodes.ReadCoil or \
            fx == ModbusFunctionCodes.WriteSingleCoil or \
            fx == ModbusFunctionCodes.WriteMultipleCoils:

            return cbData.validate_coil_address(self.cbsystem, self.cbauth,
                                                slave, address, count)
        elif fx == ModbusFunctionCodes.ReadDiscreteInput:
            return cbData.validate_discrete_input_address(self.cbsystem, self.cbauth, slave, \
                address, count)
        elif fx == ModbusFunctionCodes.ReadHoldingRegisters or \
            fx == ModbusFunctionCodes.WriteSingleHoldingRegister or \
            fx == ModbusFunctionCodes.WriteMultipleHoldingRegisters:

            return cbData.validate_holding_reg_address(self.cbsystem, self.cbauth, slave, \
                    address, count)
        elif fx == ModbusFunctionCodes.ReadInputRegisters:
            return cbData.validate_input_reg_address(self.cbsystem, self.cbauth, slave, \
                    address, count)
        else:
            raise ParameterException("Invalid function code received for \
                CbModbusDatastore.validate request. Function code received = "
                                     + str(fx))

        return True
Esempio n. 4
0
    def getValues(self, slave, fx, address, count=1):
        ''' Returns the requested values of the datastore

        :param address: The starting address
        :param count: The number of values to retrieve

        :returns: The requested values from a:a+c
        '''
        logging.debug("In CbModbusDatastore.getValues")

        if fx == ModbusFunctionCodes.ReadCoil or \
            fx == ModbusFunctionCodes.WriteSingleCoil or \
            fx == ModbusFunctionCodes.WriteMultipleCoils:

            return cbData.read_coils(self.cbsystem, self.cbauth, slave,
                                     address, count)
        elif fx == ModbusFunctionCodes.ReadDiscreteInput:
            return cbData.read_discrete_inputs(self.cbsystem, self.cbauth,
                                               slave, address, count)
        elif fx == ModbusFunctionCodes.ReadHoldingRegisters or \
            fx == ModbusFunctionCodes.WriteSingleHoldingRegister or \
            fx == ModbusFunctionCodes.WriteMultipleHoldingRegisters:

            return cbData.read_holding_registers(self.cbsystem, self.cbauth,
                                                 slave, address, count)
        elif fx == ModbusFunctionCodes.ReadInputRegisters:
            return cbData.read_input_registers(self.cbsystem, self.cbauth,
                                               slave, address, count)
        else:
            raise ParameterException("Invalid function code received for \
                CbModbusDatastore.getValues request. Function code received = "
                                     + str(fx))
Esempio n. 5
0
    def _write(self, address, value, **kwargs):
        """Perform the write, enforcing Defaults.Timeout around the entire transaction.  Normally
        returns None, but may raise a ModbusException or a PlcOffline if there are communications
        problems.

        Use a supplied 'unit' ID, or the one specified/deduced at construction.

        """
        self.client.timeout = True

        if not self.client.connect():
            raise PlcOffline(
                "Modbus Write to PLC %s/%6d failed: Offline; Connect failure" %
                (self.description, address))

        # Use address to deduce Holding Register or Coil (the only writable
        # entities); Statuses and Input Registers result in a pymodbus
        # ParameterException
        multi = hasattr(value, '__iter__')
        writer = None
        if 400001 <= address <= 465536:
            # 400001-465536: Holding Registers
            writer = (WriteMultipleRegistersRequest
                      if multi or self.multi else WriteSingleRegisterRequest)
            address -= 400001
        elif 40001 <= address <= 99999:
            #  40001-99999: Holding Registers
            writer = (WriteMultipleRegistersRequest
                      if multi or self.multi else WriteSingleRegisterRequest)
            address -= 40001
        elif 1 <= address <= 9999:
            #      1-9999: Coils
            writer = (
                WriteMultipleCoilsRequest if multi  # *don't* force multi
                else WriteSingleCoilRequest)
            address -= 1
        else:
            # 100001-165536: Statuses (not writable)
            # 300001-365536: Input Registers (not writable)
            # 10001-19999: Statuses (not writable)
            # 30001-39999: Input Registers (not writable)
            pass
        if not writer:
            raise ParameterException("Invalid Modbus address for write: %d" %
                                     (address))

        if writer is WriteMultipleRegistersRequest:
            # Overcome bug in 1.2.0/1.3.0 in handling single requests.  Also reifies generators.
            value = list(value) if multi else [value]

        unit = kwargs.pop('unit', self.unit)
        result = self.client.execute(
            writer(address, value, unit=unit, **kwargs))
        if isinstance(result, ExceptionResponse):
            raise ModbusException(str(result))
        assert isinstance(
            result,
            ModbusResponse), "Unexpected non-ModbusResponse: %r" % result
Esempio n. 6
0
    def __implementation(method):
        ''' Returns the requested framer

        :method: The serial framer to instantiate
        :returns: The requested serial framer
        '''
        method = method.lower()
        if method == 'rtu': return ModbusRtuFramer(ClientDecoder())
        raise ParameterException("Invalid framer method requested")
Esempio n. 7
0
    def __delitem__(self, slave):
        ''' Wrapper used to access the slave context

        :param slave: The slave context to remove
        '''
        if not self.single and (0xf7 >= slave >= 0x00):
            del self.__slaves[slave]
        else:
            raise ParameterException('slave index out of range')
Esempio n. 8
0
    def __setitem__(self, slave, context):
        ''' Used to set a new slave context

        :param slave: The slave context to set
        :param context: The new context to set for this slave
        '''
        if self.single: slave = Defaults.UnitId
        if 0xf7 >= slave >= 0x00:
            self.__slaves[slave] = context
        else:
            raise ParameterException('slave index out of range')
Esempio n. 9
0
    def __setitem__(self, slave, context):
        ''' Wrapper used to access the slave context

        :param slave: slave The context to set
        :param context: The new context to set for this slave
        '''
        if self.single: slave = 0x00
        if 0xf7 >= slave >= 0x00:
            self.__slaves[slave] = context
        else:
            raise ParameterException('slave index out of range')
Esempio n. 10
0
    def __getitem__(self, slave):
        ''' Wrapper used to access the slave context

        :param slave: The slave context to get
        :returns: The requested slave context
        '''
        if self.single: slave = 0x00
        if slave in self.__slaves:
            return self.__slaves.get(slave)
        else:
            raise ParameterException(
                "slave does not exist, or is out of range")
Esempio n. 11
0
    def fromCoils(klass, coils, byteorder=Endian.Little):
        """ Initialize a payload decoder with the result of
        reading a collection of coils from a modbus device.

        The coils are treated as a list of bit(boolean) values.

        :param coils: The coil results to initialize with
        :param byteorder: The endianess of the payload
        :returns: An initialized PayloadDecoder
        """
        if isinstance(coils, list):
            payload = pack_bitstring(coils)
            return klass(payload, byteorder)
        raise ParameterException('Invalid collection of coils supplied')
Esempio n. 12
0
    def __init__(self, values):
        ''' Initializes the datastore

        Using the input values we create the default
        datastore value and the starting address

        :param values: Either a list or a dictionary of values
        '''
        if isinstance(values, dict):
            self.values = values
        elif hasattr(values, '__iter__'):
            self.values = dict(enumerate(values))
        else: raise ParameterException(
            "Values for datastore must be a list or dictionary")
        self.default_value = self.values.itervalues().next().__class__()
        self.address = self.values.iterkeys().next()
Esempio n. 13
0
    def __implementation(method, client):
        """ Returns the requested framer

        :method: The serial framer to instantiate
        :returns: The requested serial framer
        """
        method = method.lower()
        if method == 'ascii':
            return ModbusAsciiFramer(ClientDecoder(), client)
        elif method == 'rtu':
            return ModbusRtuFramer(ClientDecoder(), client)
        elif method == 'binary':
            return ModbusBinaryFramer(ClientDecoder(), client)
        elif method == 'socket':
            return ModbusSocketFramer(ClientDecoder(), client)
        raise ParameterException("Invalid framer method requested")
Esempio n. 14
0
    def from_registers(registers, endian=Endian.Little):
        """ Initialize a payload decoder with the result of
        reading a collection of registers from a modbus device.

        The registers are treated as a list of 2 byte values.
        We have to do this because of how the data has already
        been decoded by the rest of the library.

        :param registers: The register results to initialize with
        :param endian: The endianess of the payload
        :returns: An initialized PayloadDecoder
        """
        if isinstance(registers, list):  # repack into flat binary
            payload = ''.join(pack('>H', x) for x in registers)
            return ModiconPayloadDecoder(payload, endian)
        raise ParameterException('Invalid collection of registers supplied')
Esempio n. 15
0
    def fromRegisters(klass, registers, byteorder=Endian.Little,
                      wordorder=Endian.Big):
        """ Initialize a payload decoder with the result of
        reading a collection of registers from a modbus device.

        The registers are treated as a list of 2 byte values.
        We have to do this because of how the data has already
        been decoded by the rest of the library.

        :param registers: The register results to initialize with
        :param byteorder: The Byte order of each word
        :param wordorder: The endianess of the word (when wordcount is >= 2)
        :returns: An initialized PayloadDecoder
        """
        if isinstance(registers, list):  # repack into flat binary
            payload = b''.join(pack('!H', x) for x in registers)
            return klass(payload, byteorder, wordorder)
        raise ParameterException('Invalid collection of registers supplied')
Esempio n. 16
0
    def setValues(self, slave, fx, address, values):
        ''' Sets the requested values of the datastore

        :param address: The starting address
        :param values: The new values to be set
        '''
        logging.debug("In CbModbusDatastore.setValues")

        if fx == ModbusFunctionCodes.WriteSingleCoil or \
            fx == ModbusFunctionCodes.WriteMultipleCoils:

            cbData.write_coils(self.cbsystem, self.cbauth, slave, address, values)
        elif fx == ModbusFunctionCodes.WriteSingleHoldingRegister or \
            fx == ModbusFunctionCodes.WriteMultipleHoldingRegisters:

            cbData.write_holding_registers(self.cbsystem, self.cbauth, slave, address, values)
        else:
            raise ParameterException("Invalid function code received for \
                CbModbusDatastore.setValues request.  Function code received = " + str(fx))
Esempio n. 17
0
    def __init__(self, context, register_type, values):
        """
        Initializes the sparse datastore.
        Using the input values it creates the default datastore value and the starting address

        :param context.ClearBladeModbusProxySlaveContext context: the parent/context of the data block
        :param str register_type: select from hr, ir, di, co
        :param dict values: Either a dictionary or list of values
        """
        super(CbModbusSparseDataBlock, self).__init__(values=values)
        self.context = context
        if register_type in REGISTER_TYPES:
            self.register_type = register_type
        else:
            raise ParameterException(
                "Register type must be one of: ".format(REGISTER_TYPES))
        self.timestamps = {}
        for addr in self.values:
            self.timestamps[addr] = None
Esempio n. 18
0
    def __init__(self, context, register_type, address, values):
        """
        Initializes the sequential datastore

        :param context.ClearBladeModbusProxySlaveContext context: the parent/context of the data block
        :param str register_type: select from hr, ir, di, co
        :param int address: the starting address for the sequential block
        :param iterable values: Either a dictionary or list of values
        """
        super(CbModbusSequentialDataBlock, self).__init__(address=address,
                                                          values=values)
        self.context = context
        if register_type in REGISTER_TYPES:
            self.register_type = register_type
        else:
            raise ParameterException(
                "Register type must be one of: ".format(REGISTER_TYPES))
        self.timestamps = []
        for i in range(0, len(self.values) - 1):
            self.timestamps.append(None)
Esempio n. 19
0
    def _process_values(self, values):
        def _process_as_dict(values):
            for idx, val in iteritems(values):
                if isinstance(val, (list, tuple)):
                    for i, v in enumerate(val):
                        self.values[idx + i] = v
                else:
                    self.values[idx] = int(val)

        if isinstance(values, dict):
            _process_as_dict(values)
            return
        if hasattr(values, '__iter__'):
            values = dict(enumerate(values))
        elif values is None:
            values = {}  # Must make a new dict here per instance
        else:
            raise ParameterException("Values for datastore must "
                                     "be a list or dictionary")
        _process_as_dict(values)
Esempio n. 20
0
    def fromCoils(klass, coils, byteorder=Endian.Little, wordorder=Endian.Big):
        """ Initialize a payload decoder with the result of
        reading a collection of coils from a modbus device.

        The coils are treated as a list of bit(boolean) values.

        :param coils: The coil results to initialize with
        :param byteorder: The endianess of the payload
        :returns: An initialized PayloadDecoder
        """
        if isinstance(coils, list):
            payload = b''
            padding = len(coils) % 8
            if padding:  # Pad zero's
                extra = [False] * padding
                coils = extra + coils
            chunks = klass.bit_chunks(coils)
            for chunk in chunks:
                payload += pack_bitstring(chunk[::-1])
            return klass(payload, byteorder)
        raise ParameterException('Invalid collection of coils supplied')
Esempio n. 21
0
def read_collection_data(context, register_type, address, count, fill=0):
    """
    Retrieve data from the specified collection.
    When retrieving sequential blocks if the ClearBlade collection is missing registers between the start and end,
    those will be filled (optionally)

    :param context.ClearBladeModbusProxySlaveContext context: The ClearBlade parent metadata to query against.
    :param str register_type: the type of register ('co', 'di', 'hr', 'ir')
    :param int address: The starting address
    :param int count: The number of values to retrieve
    :param int fill: automatically fills gaps in sequential register blocks with this value (or None)
    :returns: values, timestamps of the data read from the ClearBlade collection/proxy
    :rtype: list or dict (sequential or sparse)
    """
    collection = context.cb_system.Collection(
        context.cb_auth, collectionName=context.cb_data_collection)
    query = Query()
    query.equalTo(COL_PROXY_IP_ADDRESS, context.ip_proxy)
    query.equalTo(COL_SLAVE_ID, context.slave_id)
    query.equalTo(COL_REG_TYPE, register_type)
    if count > 1:
        query.greaterThanEqualTo(COL_REG_ADDRESS, address)
        query.lessThan(COL_REG_ADDRESS, address + count)
    else:
        query.equalTo(COL_REG_ADDRESS, address)
    reg_list = sorted(collection.getItems(query),
                      key=lambda k: k[COL_REG_ADDRESS])
    if len(reg_list) != count:
        context.log.warning(
            "Got {} rows from ClearBlade, expecting {} registers".format(
                len(reg_list), count))
    if context.sparse:
        values = {}
        timestamps = {}
        # Below commented code would fill in missing registers in a sparse data block (placeholder needs more thought)
        # for i in range(0, count-1):
        #     curr_addr = reg_list[i][COL_REG_ADDRESS]
        #     values[curr_addr] = reg_list[i][COL_REG_DATA]
        #     if i+1 < count and i+1 < len(reg_list):
        #         next_addr = curr_addr + 1
        #         if reg_list[i+1][COL_REG_ADDRESS] != next_addr and fill:
        #             context.log.info("Filling {} register {} with value {}"
        #                              .format(register_type, next_addr, FILL_VALUE))
        #             values[next_addr] = FILL_VALUE
        for reg in reg_list:
            values[reg[COL_REG_ADDRESS]] = reg[COL_REG_DATA]
            timestamps[reg[COL_REG_ADDRESS]] = reg[COL_DATA_TIMESTAMP]
    else:
        values = []
        timestamps = []
        for addr in range(address, address + count):
            if reg_list[addr][COL_REG_ADDRESS] != addr:
                if fill is not None:
                    if isinstance(fill, int):
                        context.log.info(
                            "Filling {} register {} with value {}".format(
                                register_type, addr, fill))
                        reg_list.insert(addr, {COL_REG_DATA: fill})
                    else:
                        raise ValueError(
                            "Fill parameter must be integer or None")
                else:
                    raise ParameterException(
                        "ClearBlade Collection missing register {} from block [{}:{}]"
                        .format(addr, address, address + count))
            values.append(reg_list[addr][COL_REG_DATA])
            timestamps.append(reg_list[addr][COL_DATA_TIMESTAMP])
    return values, timestamps
Esempio n. 22
0
    def _read(self, address, count=1, **kwargs):
        """Perform the read, enforcing Defaults.Timeout around the entire transaction.  Returns the
        result bit(s)/register(s), or raises an Exception; probably a ModbusException or a
        PlcOffline for communications errors, but could be some other type of Exception.

        Use a supplied 'unit' ID, or the one specified/deduced at construction.

        """
        self.client.timeout = True

        if not self.client.connect():
            raise PlcOffline(
                "Modbus Read  of PLC %s/%6d failed: Offline; Connect failure" %
                (self.description, address))

        # Use address to deduce Holding/Input Register or Coil/Status.
        reader = None
        xformed = address
        if 400001 <= address <= 465536:
            reader = ReadHoldingRegistersRequest
            xformed -= 400001
        elif 300001 <= address <= 365536:
            reader = ReadInputRegistersRequest
            xformed -= 300001
        elif 100001 <= address <= 165536:
            reader = ReadDiscreteInputsRequest
            xformed -= 100001
        elif 40001 <= address <= 99999:
            reader = ReadHoldingRegistersRequest
            xformed -= 40001
        elif 30001 <= address <= 39999:
            reader = ReadInputRegistersRequest
            xformed -= 30001
        elif 10001 <= address <= 19999:
            reader = ReadDiscreteInputsRequest
            xformed -= 10001
        elif 1 <= address <= 9999:
            reader = ReadCoilsRequest
            xformed -= 1
        else:
            # Invalid address
            pass
        if not reader:
            raise ParameterException("Invalid Modbus address for read: %d" %
                                     (address))

        unit = kwargs.pop('unit', self.unit)
        request = reader(xformed, count, unit=unit, **kwargs)
        log.debug("%s/%6d-%6d transformed to %s", self.description, address,
                  address + count - 1, request)

        result = self.client.execute(request)
        if isinstance(result, ExceptionResponse):
            # The remote PLC returned a response indicating it encountered an
            # error processing the request.  Convert it to raise a ModbusException.
            raise ModbusException(str(result))
        assert isinstance(
            result,
            ModbusResponse), "Unexpected non-ModbusResponse: %r" % result

        # The result may contain .bits or .registers,  1 or more values
        values = result.bits if hasattr(result, 'bits') else result.registers
        return values if len(values) > 1 else values[0]