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)
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 _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))
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
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]})
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
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...")