Exemplo n.º 1
0
def read_collection_data(cbsystem, cbauth, slave, address, count, collection):
    """Retrieve data from the specified collection

    :param cbsystem: The ClearBlade system object representing the ClearBlade System the adapter
                     will communicate with.
    :param cbauth: The object representing the ClearBlade Platform authentication credentials.
    :param slave: The unit id of the modbus device validation should be performed against.
    :param address: The starting address
    :param count: The number of values to retrieve
    :param collection: The name of the ClearBlade platform data collection in which to query

    :returns: Rows from the named data collection associated with the specified slave and data
              addresses
    """
    logging.debug("Begin read_collection_data")

    collection = cbsystem.Collection(cbauth, collectionName=collection)

    the_query = Query()
    the_query.equalTo("unit_id", slave)

    if count > 1:
        the_query.greaterThanEqualTo("data_address", address)
        the_query.lessThan("data_address", address + count)
    else:
        the_query.equalTo("data_address", address)

    return collection.getItems(the_query)
Exemplo n.º 2
0
def write_collection_data(context, register_type, address, data):
    """
    Retrieve input register values from the Analog_Input_Registers collection

    :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 data: The data value(s) to write
    """
    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)
    query.equalTo(COL_REG_ADDRESS, address)
    collection.updateItems(query, {COL_REG_DATA: data})
def get_adapter_config():
    """Retrieve the runtime configuration for the adapter from a ClearBlade Platform data \
    collection"""
    logging.debug("Begin get_adapter_config")

    logging.debug('Retrieving the adapter configuration from data collection %s', \
        CB_CONFIG['adapterSettingsCollectionName'])

    collection = CB_SYSTEM.Collection(CB_AUTH,
                                      collectionName=CB_CONFIG['adapterSettingsCollectionName'])

    the_query = Query()
    if CB_CONFIG['adapterSettingsItemID'] != "":
        the_query.equalTo("item_id", CB_CONFIG['adapterSettingsItemID'])

    rows = collection.getItems(the_query)

    # Iterate through rows and display them
    for row in rows:
        logging.debug(row)

    logging.debug("End get_adapter_config")
    def __fetch_adapter_config(self):
        cbLogs.info("AdapterLibrary - __fetch_adapter_config - Retrieving adapter config")

        adapter_config = {"topic_root": self.adapter_name, "adapter_settings": ""}

        collection = self._cb_system.Collection(self._device_client, collectionName=self._args[self.ADAPTER_CONFIG_COLLECTION_NAME_ARG_KEY])

        query = Query()
        query.equalTo("adapter_name", self.adapter_name)

        rows = collection.getItems(query)

        if len(rows) == 1:
            if rows[0]["topic_root"] != "":
                adapter_config["topic_root"] = str(rows[0]["topic_root"])
            if rows[0]["adapter_settings"] != "":
                raw_json = json.loads(str(rows[0]["adapter_settings"]))
                adapter_config["adapter_settings"] = self.__byteify(raw_json)
        else:
            cbLogs.warn("No adapter config found for adapter name " + self.adapter_name + ". Using defaults")

        cbLogs.info("AdapterLibrary - __fetch_adapter_config - Using adapter config: " + str(adapter_config))
        return adapter_config
Exemplo n.º 5
0
 def _initialize_slaves(self):
     """Sets up the slave contexts for the server"""
     slaves = []
     collection = self.cb_system.Collection(self.cb_auth, collectionName=self.cb_slaves)
     query = Query()
     if self.ip_address is not None:
         self.log.debug("Querying ClearBlade based on ip_address: {}".format(self.ip_address))
         query.equalTo(COL_PROXY_IP_ADDRESS, self.ip_address)
     else:
         self.log.debug("No ip_address found in ClearBlade, querying based on non-empty slave_id")
         query.notEqualTo(COL_SLAVE_ID, '')
     rows = collection.getItems(query)
     self.log.debug("Found {} rows in ClearBlade adapter config".format(len(rows)))
     for row in rows:
         slave_id = int(row[COL_SLAVE_ID])
         if slave_id not in slaves:
             slaves.append(slave_id)
             self[slave_id] = ClearBladeModbusProxySlaveContext(server_context=self, config=row, log=self.log)
         else:
             self.log.warning("Duplicate slave_id {} found in RTUs collection - only 1 RTU per server context"
                              .format(slave_id))
Exemplo n.º 6
0
def write_collection_data(cbsystem, cbauth, slave, address, data, collection):
    """Retrieve input register values from the Analog_Input_Registers collection

    :param cbsystem: The ClearBlade system object representing the ClearBlade System the adapter
                     will communicate with.
    :param cbauth: The object representing the ClearBlade Platform authentication credentials.
    :param slave: The unit id of the modbus device validation should be performed against.
    :param address: The starting address
    :param data: The data values to write to the Analog_Output_Holding_Registers collection
    :param collection: The name of the ClearBlade platform data collection in which to write
                        the data to
    """
    logging.debug("Begin write_collection_data")

    collection = cbsystem.Collection(cbauth, collectionName=collection)

    for ndx in range(0, len(data)):

        the_query = Query()
        the_query.equalTo("unit_id", slave)
        the_query.equalTo("data_address", address + ndx)

        collection.updateItems(the_query, {"data_value": data[ndx]})
Exemplo n.º 7
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
Exemplo n.º 8
0
def run_async_server():
    """
    The main loop instantiates one or more PyModbus servers mapped to ClearBlade Modbus proxies based on
    IP address and port defined in a ClearBlade platform Collection
    """
    log = None
    virtual_ifs = []
    err_msg = None
    defer_reactor = False

    try:
        parser = get_parser()
        user_options = parser.parse_args()
        local_ip_address = user_options.ip_address
        local_tcp_port = user_options.tcp_port
        net_if = user_options.net_if
        if user_options.log_level == 'DEBUG':
            _debug = True
        else:
            _debug = False
        HEARTBEAT = user_options.heartbeat

        log = headless.get_wrapping_logger(name=ADAPTER_DEVICE_ID, debug=_debug)
        server_log = headless.get_wrapping_logger(name="pymodbus.server", debug=_debug)

        log.info("Initializing ClearBlade System connection")
        cb_system = System(systemKey=user_options.systemKey, systemSecret=user_options.systemSecret, url=user_options.url)
        cb_auth = cb_system.Device(name=user_options.deviceName, key=user_options.deviceKey)
        cb_slave_config = user_options.slaves_collection
        cb_data = user_options.data_collection

        ip_proxies = []
        proxy_ports = []
        ip_address = None
        collection = cb_system.Collection(cb_auth, collectionName=cb_slave_config)
        query = Query()
        query.notEqualTo(COL_PROXY_IP_ADDRESS, '')
        rows = collection.getItems(query)
        for row in rows:
            # TODO: allow for possibility of multiple IPs with same port or same IP with multiple ports
            ip_address = str(row[COL_PROXY_IP_ADDRESS])
            tcp_port = int(row[COL_PROXY_IP_PORT])
            if ip_address not in ip_proxies:
                log.info("Found slave at {} on ClearBlade adapter config".format(ip_address))
                ip_proxies.append(ip_address)
                proxy_ports.append(tcp_port)
            else:
                log.warning("Duplicate proxy IP address {} found in configuration - ignoring".format(ip_address))

        log.debug("Processing {} slaves".format(len(ip_proxies)))
        for i in range(0, len(ip_proxies)):
            log.debug("Getting server context for {}".format(ip_proxies[i]))
            context = ClearBladeModbusProxyServerContext(cb_system=cb_system, cb_auth=cb_auth,
                                                         cb_slaves_config=cb_slave_config, cb_data=cb_data,
                                                         ip_address=ip_proxies[i], log=log)
            # Create IP aliases
            local_ip_address = ip_proxies[i]
            ip_mask = '255.255.255.0'
            local_tcp_port = proxy_ports[i]
            if sys.platform.startswith('win'):
                log.info("I'm on Windows!")
                local_ip_address = 'localhost'
            elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
                virtual_if = '{nif}:{alias}'.format(nif=net_if, alias=i)
                virtual_ifs.append(virtual_if)
                linux_command = "ifconfig {vif} {ip}".format(vif=virtual_if, ip=local_ip_address)
                if ip_mask is not None:
                    linux_command += " netmask {mask}".format(mask=ip_mask)
                log.info("Creating virtual IP address / alias via $ {}".format(linux_command))
                subprocess.call(linux_command, shell=True)

            # Create Server Identification
            identity = ModbusDeviceIdentification()
            identity.VendorName = 'PyModbus'
            identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
            identity.ProductName = 'Inmarsat/ClearBlade Modbus Server Adapter'
            identity.ModelName = ip_proxies[i]
            identity.MajorMinorRevision = '1.0'

            # Setup Modbus TCP Server
            log.info("Starting Modbus TCP server on {}:{}".format(local_ip_address, local_tcp_port))
            modbus_server_args = {
                'context': context,
                'identity': identity,
                'address': (local_ip_address, local_tcp_port),
                # 'console': _debug,
                'defer_reactor_run': True,
            }
            if modbus_server_args['defer_reactor_run']:
                defer_reactor = True
            reactor.callInThread(StartTcpServer, **modbus_server_args)

            if local_ip_address == 'localhost':
                log.info("Windows retricted environment prevents IP alias - running localhost for {}"
                         .format(ip_proxies[i]))
                break

        reactor.callInThread(_heartbeat, log, time.time(), HEARTBEAT)
        if defer_reactor:
            reactor.suggestThreadPoolSize(len(ip_proxies))
            reactor.run()

    except KeyboardInterrupt:
        err_msg = "modbus_server_adapter.py halted by Keyboard Interrupt"
        if log is not None:
            log.info(err_msg)
        else:
            print(err_msg)
        sys.exit("modbus_server_adapter.py halted by Keyboard Interrupt")

    except Exception as e:
        err_msg = "EXCEPTION: {}".format(e)
        if log is not None:
            log.info(err_msg)
        else:
            print(err_msg)
        sys.exit("modbus_server_adapter.py halted by exception {}".format(e))

    finally:
        if defer_reactor and reactor.running:
            reactor.stop()
        for vif in virtual_ifs:
            debug_msg = "Taking down virtual interface {}".format(vif)
            if log is not None:
                log.debug(debug_msg)
            else:
                print(debug_msg)
            linux_command = "ifconfig {} down".format(vif)
            subprocess.call(linux_command, shell=True)
        print("Exiting...")