def server_side_rpc_handler(self, content): try: if content.get("device") is not None: log.debug("Modbus connector received rpc request for %s with content: %s", content["device"], content) if isinstance(self.__devices[content["device"]]["config"]["rpc"], dict): rpc_command_config = self.__devices[content["device"]]["config"]["rpc"].get(content["data"]["method"]) if rpc_command_config is not None: self.__process_rpc_request(content, rpc_command_config) elif isinstance(self.__devices[content["device"]]["config"]["rpc"], list): for rpc_command_config in self.__devices[content["device"]]["config"]["rpc"]: if rpc_command_config["tag"] == content["data"]["method"]: self.__process_rpc_request(content, rpc_command_config) break else: log.error("Received rpc request, but method %s not found in config for %s.", content["data"].get("method"), self.get_name()) self.__gateway.send_rpc_reply(content["device"], content["data"]["id"], {content["data"]["method"]: "METHOD NOT FOUND!"}) else: log.debug("Received RPC to connector: %r", content) except Exception as e: log.exception(e)
def __load_converters(self): try: for device in self.__config["devices"]: if self.__config.get("converter") is not None: converter = TBUtility.check_and_import(self._connector_type, self.__config["converter"])(device) else: converter = BytesModbusUplinkConverter(device) if self.__config.get("downlink_converter") is not None: downlink_converter = TBUtility.check_and_import(self._connector_type, self.__config["downlink_converter"])(device) else: downlink_converter = BytesModbusDownlinkConverter(device) if device.get('deviceName') not in self.__gateway.get_devices(): self.__gateway.add_device(device.get('deviceName'), {"connector": self}, device_type=device.get("deviceType")) self.__devices[device["deviceName"]] = {"config": device, "converter": converter, "downlink_converter": downlink_converter, "next_attributes_check": 0, "next_timeseries_check": 0, "telemetry": {}, "attributes": {}, "last_telemetry": {}, "last_attributes": {} } except Exception as e: log.exception(e)
def convert(self, config, data): self.__result["telemetry"] = [] self.__result["attributes"] = [] for config_data in data: for tag in data[config_data]: configuration = data[config_data][tag]["data_sent"] response = data[config_data][tag]["input_data"] byte_order = configuration.get("byteOrder", "LITTLE") endian_order = Endian.Little if byte_order.upper( ) == "LITTLE" else Endian.Big decoded_data = None if not isinstance(response, ModbusIOException): if configuration["functionCode"] in [1, 2]: result = response.bits result = result if byte_order.upper( ) == 'LITTLE' else result[::-1] log.debug(result) if "bit" in configuration: decoded_data = result[configuration["bit"]] else: decoded_data = result[0] elif configuration["functionCode"] in [3, 4]: decoder = None registers = response.registers log.debug("Tag: %s Config: %s registers: %s", tag, str(configuration), str(registers)) try: decoder = BinaryPayloadDecoder.fromRegisters( registers, byteorder=endian_order) except TypeError: # pylint: disable=E1123 decoder = BinaryPayloadDecoder.fromRegisters( registers, endian=endian_order) assert decoder is not None decoded_data = self.__decode_from_registers( decoder, configuration) if configuration.get("divider"): decoded_data = float(decoded_data) / float( configuration["divider"]) if configuration.get("multiplier"): decoded_data = decoded_data / configuration[ "multiplier"] else: log.exception(response) decoded_data = None log.debug("datatype: %s \t key: %s \t value: %s", self.__datatypes[config_data], tag, str(decoded_data)) self.__result[self.__datatypes[config_data]].append( {tag: decoded_data}) log.debug(self.__result) return self.__result
def on_attributes_update(self, content): try: for attribute_updates_command_config in self.__devices[content["device"]]["config"]["attributeUpdates"]: for attribute_updated in content["data"]: if attribute_updates_command_config["tag"] == attribute_updated: to_process = { "device": content["device"], "data": { "method": attribute_updated, "params": content["data"][attribute_updated] } } self.__process_rpc_request(to_process, attribute_updates_command_config) except Exception as e: log.exception(e)
def __function_to_device(self, config, unit_id): function_code = config.get('functionCode') result = None if function_code in (1, 2, 3, 4): result = self.__available_functions[function_code](config["address"], config.get("objectsCount", config.get("registersCount", config.get("registerCount", 1))), unit=unit_id) elif function_code in (5, 6, 15, 16): result = self.__available_functions[function_code](config["address"], config["payload"], unit=unit_id) else: log.error("Unknown Modbus function with code: %i", function_code) log.debug("With result %s", str(result)) if "Exception" in str(result): log.exception(result) result = str(result) return result
def __process_rpc_request(self, content, rpc_command_config): if rpc_command_config is not None: rpc_command_config["unitId"] = self.__devices[content["device"]]["config"]["unitId"] self.__connect_to_current_master(content["device"]) # if rpc_command_config.get('bit') is not None: # rpc_command_config["functionCode"] = 6 if rpc_command_config.get("functionCode") in (5, 6, 15, 16): rpc_command_config["payload"] = self.__devices[content["device"]]["downlink_converter"].convert( rpc_command_config, content) response = None try: response = self.__function_to_device(rpc_command_config, rpc_command_config["unitId"]) except Exception as e: log.exception(e) response = e if isinstance(response, (ReadRegistersResponseBase, ReadBitsResponseBase)): to_converter = {"rpc": {content["data"]["method"]: {"data_sent": rpc_command_config, "input_data": response}}} response = self.__devices[content["device"]]["converter"].convert(config={**self.__devices[content["device"]]["config"], "byteOrder": self.__devices[content["device"]]["config"].get("byteOrder", self.__byte_order), "wordOrder": self.__devices[content["device"]]["config"].get("wordOrder", self.__word_order)}, data=to_converter) log.debug("Received RPC method: %s, result: %r", content["data"]["method"], response) # response = {"success": response} elif isinstance(response, (WriteMultipleRegistersResponse, WriteMultipleCoilsResponse, WriteSingleCoilResponse, WriteSingleRegisterResponse)): log.debug("Write %r", str(response)) response = {"success": True} if content.get("id") or (content.get("data") is not None and content["data"].get("id")): if isinstance(response, Exception): self.__gateway.send_rpc_reply(content["device"], content["data"]["id"], {content["data"]["method"]: str(response)}) else: self.__gateway.send_rpc_reply(content["device"], content["data"]["id"], response) log.debug("%r", response)
def convert(self, config, data): byte_order_str = config.get("byteOrder", "LITTLE") byte_order = Endian.Big if byte_order_str.upper( ) == "BIG" else Endian.Little builder = BinaryPayloadBuilder(byteorder=byte_order) builder_functions = { "string": builder.add_string, "bits": builder.add_bits, "8int": builder.add_8bit_int, "16int": builder.add_16bit_int, "32int": builder.add_32bit_int, "64int": builder.add_64bit_int, "8uint": builder.add_8bit_uint, "16uint": builder.add_16bit_uint, "32uint": builder.add_32bit_uint, "64uint": builder.add_64bit_uint, "16float": builder.add_16bit_float, "32float": builder.add_32bit_float, "64float": builder.add_64bit_float } value = None if data.get("data") and data["data"].get("params") is not None: value = data["data"]["params"] else: value = config["value"] lower_type = config.get("type", config.get("tag", "error")).lower() if lower_type == "error": log.error('"type" and "tag" - not found in configuration.') variable_size = config.get("registerCount", 1) * 8 if lower_type in ["integer", "dword", "dword/integer", "word", "int"]: lower_type = str(variable_size) + "int" assert builder_functions.get(lower_type) is not None builder_functions[lower_type](value) elif lower_type in [ "uint", "unsigned", "unsigned integer", "unsigned int" ]: lower_type = str(variable_size) + "uint" assert builder_functions.get(lower_type) is not None builder_functions[lower_type](value) elif lower_type in ["float", "double"]: lower_type = str(variable_size) + "float" assert builder_functions.get(lower_type) is not None builder_functions[lower_type](value) elif lower_type in ["coil", "bits"]: assert builder_functions.get("bits") is not None builder_functions["bits"](value) elif lower_type in ["string"]: assert builder_functions.get("string") is not None builder_functions[lower_type](value) elif lower_type in ["bit"]: bits = [0 for _ in range(8)] bits[config["bit"] - 1] = int(value) log.debug(bits) builder.add_bits(bits) return builder.to_string() else: log.error("Unknown variable type") builder_converting_functions = { 5: builder.to_coils, 15: builder.to_coils, 6: builder.to_registers, 16: builder.to_registers } function_code = config["functionCode"] if function_code in builder_converting_functions: builder = builder_converting_functions[function_code]() if "Exception" in str(builder): log.exception(builder) builder = str(builder) return builder log.warning( "Unsupported function code, for the device %s in the Modbus Downlink converter", config["device"]) return None
def convert(self, config, data): byte_order_str = config.get("byteOrder", "LITTLE") word_order_str = config.get("wordOrder", "LITTLE") byte_order = Endian.Big if byte_order_str.upper( ) == "BIG" else Endian.Little word_order = Endian.Big if word_order_str.upper( ) == "BIG" else Endian.Little repack = config.get("repack", False) builder = BinaryPayloadBuilder(byteorder=byte_order, wordorder=word_order, repack=repack) builder_functions = { "string": builder.add_string, "bits": builder.add_bits, "8int": builder.add_8bit_int, "16int": builder.add_16bit_int, "32int": builder.add_32bit_int, "64int": builder.add_64bit_int, "8uint": builder.add_8bit_uint, "16uint": builder.add_16bit_uint, "32uint": builder.add_32bit_uint, "64uint": builder.add_64bit_uint, "16float": builder.add_16bit_float, "32float": builder.add_32bit_float, "64float": builder.add_64bit_float } value = None if data.get("data") and data["data"].get("params") is not None: value = data["data"]["params"] else: value = config.get("value", 0) lower_type = config.get("type", config.get("tag", "error")).lower() if lower_type == "error": log.error('"type" and "tag" - not found in configuration.') variable_size = config.get( "objectsCount", config.get("registersCount", config.get("registerCount", 1))) * 16 if lower_type in ["integer", "dword", "dword/integer", "word", "int"]: lower_type = str(variable_size) + "int" assert builder_functions.get(lower_type) is not None builder_functions[lower_type](int(value)) elif lower_type in [ "uint", "unsigned", "unsigned integer", "unsigned int" ]: lower_type = str(variable_size) + "uint" assert builder_functions.get(lower_type) is not None builder_functions[lower_type](int(value)) elif lower_type in ["float", "double"]: lower_type = str(variable_size) + "float" assert builder_functions.get(lower_type) is not None builder_functions[lower_type](float(value)) elif lower_type in ["coil", "bits", "coils", "bit"]: assert builder_functions.get("bits") is not None if variable_size / 8 > 1.0: builder_functions["bits"](bytes(value, encoding='UTF-8')) if isinstance(value, str) else \ builder_functions["bits"](bytes(value)) else: return bytes(int(value)) elif lower_type in ["string"]: assert builder_functions.get("string") is not None builder_functions[lower_type](value) elif lower_type in builder_functions and 'int' in lower_type: builder_functions[lower_type](int(value)) elif lower_type in builder_functions and 'float' in lower_type: builder_functions[lower_type](float(value)) elif lower_type in builder_functions: builder_functions[lower_type](value) else: log.error("Unknown variable type") builder_converting_functions = { 5: builder.to_coils, 15: builder.to_coils, 6: builder.to_registers, 16: builder.to_registers } function_code = config["functionCode"] if function_code in builder_converting_functions: builder = builder_converting_functions[function_code]() log.debug(builder) if "Exception" in str(builder): log.exception(builder) builder = str(builder) if variable_size <= 16: if isinstance(builder, list) and len(builder) not in (8, 16, 32, 64): builder = builder[0] else: if isinstance(builder, list) and len(builder) not in (2, 4): log.warning( "There is a problem with the value builder. Only the first register is written." ) builder = builder[0] return builder log.warning( "Unsupported function code, for the device %s in the Modbus Downlink converter", config["device"]) return None
def convert(self, config, data): self.__result["telemetry"] = [] self.__result["attributes"] = [] for config_data in data: for tag in data[config_data]: try: configuration = data[config_data][tag]["data_sent"] response = data[config_data][tag]["input_data"] if configuration.get("byteOrder"): byte_order = configuration["byteOrder"] elif config.get("byteOrder"): byte_order = config["byteOrder"] else: byte_order = "LITTLE" if configuration.get("wordOrder"): word_order = configuration["wordOrder"] elif config.get("wordOrder"): word_order = config.get("wordOrder", "BIG") else: word_order = "BIG" endian_order = Endian.Little if byte_order.upper( ) == "LITTLE" else Endian.Big word_endian_order = Endian.Little if word_order.upper( ) == "LITTLE" else Endian.Big decoded_data = None if not isinstance(response, ModbusIOException) and not isinstance( response, ExceptionResponse): if configuration["functionCode"] in [1, 2]: result = response.bits result = result if byte_order.upper( ) == 'LITTLE' else result[::-1] log.debug(result) if configuration["type"].lower() == "bits": decoded_data = result[:configuration.get( "objectsCount", configuration. get("registersCount", configuration.get("registerCount", 1) ))] if len(decoded_data) == 1 and isinstance( decoded_data, list): decoded_data = decoded_data[0] else: decoded_data = result[0] elif configuration["functionCode"] in [3, 4]: decoder = None registers = response.registers log.debug("Tag: %s Config: %s registers: %s", tag, str(configuration), str(registers)) try: decoder = BinaryPayloadDecoder.fromRegisters( registers, byteorder=endian_order, wordorder=word_endian_order) except TypeError: # pylint: disable=E1123 decoder = BinaryPayloadDecoder.fromRegisters( registers, endian=endian_order, wordorder=word_endian_order) assert decoder is not None decoded_data = self.__decode_from_registers( decoder, configuration) if configuration.get("divider"): decoded_data = float(decoded_data) / float( configuration["divider"]) if configuration.get("multiplier"): decoded_data = decoded_data * configuration[ "multiplier"] else: log.exception(response) decoded_data = None if config_data == "rpc": return decoded_data log.debug("datatype: %s \t key: %s \t value: %s", self.__datatypes[config_data], tag, str(decoded_data)) if decoded_data is not None: self.__result[self.__datatypes[config_data]].append( {tag: decoded_data}) except Exception as e: log.exception(e) log.debug(self.__result) return self.__result
def __process_devices(self): for device in self.__devices: current_time = time.time() device_responses = {"timeseries": {}, "attributes": {}, } to_send = {} try: for config_data in device_responses: #log.info(config_data) if self.__devices[device]["config"].get(config_data) is not None: unit_id = self.__devices[device]["config"]["unitId"] if self.__devices[device]["next_"+config_data+"_check"] < current_time: self.__connect_to_current_master(device) # Reading data from device for interested_data in range(len(self.__devices[device]["config"][config_data])): current_data = self.__devices[device]["config"][config_data][interested_data] current_data["deviceName"] = device input_data = self.__function_to_device(current_data, unit_id) # if not isinstance(input_data, ReadRegistersResponseBase) and input_data.isError(): # log.exception(input_data) # continue device_responses[config_data][current_data["tag"]] = {"data_sent": current_data, "input_data": input_data} log.debug("Checking %s for device %s", config_data, device) self.__devices[device]["next_"+config_data+"_check"] = current_time + self.__devices[device]["config"][config_data+"PollPeriod"]/1000 log.debug(device_responses) converted_data = {} try: converted_data = self.__devices[device]["converter"].convert(config={**self.__devices[device]["config"], "byteOrder": self.__devices[device]["config"].get("byteOrder", self.__byte_order), "wordOrder": self.__devices[device]["config"].get("wordOrder", self.__word_order)}, data=device_responses) except Exception as e: log.error(e) if converted_data and self.__devices[device]["config"].get("sendDataOnlyOnChange"): self.statistics['MessagesReceived'] += 1 to_send = {"deviceName": converted_data["deviceName"], "deviceType": converted_data["deviceType"]} if to_send.get("telemetry") is None: to_send["telemetry"] = [] if to_send.get("attributes") is None: to_send["attributes"] = [] for telemetry_dict in converted_data["telemetry"]: for key, value in telemetry_dict.items(): if self.__devices[device]["last_telemetry"].get(key) is None or \ self.__devices[device]["last_telemetry"][key] != value: self.__devices[device]["last_telemetry"][key] = value to_send["telemetry"].append({key: value}) for attribute_dict in converted_data["attributes"]: for key, value in attribute_dict.items(): if self.__devices[device]["last_attributes"].get(key) is None or \ self.__devices[device]["last_attributes"][key] != value: self.__devices[device]["last_attributes"][key] = value to_send["attributes"].append({key: value}) # to_send["telemetry"] = converted_data["telemetry"] # if converted_data["attributes"] != self.__devices[device]["attributes"]: # self.__devices[device]["last_attributes"] = converted_data["attributes"] # to_send["attributes"] = converted_data["attributes"] if not to_send.get("attributes") and not to_send.get("telemetry"): # self.__gateway.send_to_storage(self.get_name(), to_send) # self.statistics['MessagesSent'] += 1 log.debug("Data has not been changed.") elif converted_data and self.__devices[device]["config"].get("sendDataOnlyOnChange") is None or not self.__devices[device]["config"].get("sendDataOnlyOnChange"): self.statistics['MessagesReceived'] += 1 to_send = {"deviceName": converted_data["deviceName"], "deviceType": converted_data["deviceType"]} # if converted_data["telemetry"] != self.__devices[device]["telemetry"]: self.__devices[device]["last_telemetry"] = converted_data["telemetry"] to_send["telemetry"] = converted_data["telemetry"] # if converted_data["attributes"] != self.__devices[device]["attributes"]: self.__devices[device]["last_attributes"] = converted_data["attributes"] to_send["attributes"] = converted_data["attributes"] # self.__gateway.send_to_storage(self.get_name(), to_send) # self.statistics['MessagesSent'] += 1 #log.info(to_send) # 数据添加到存储队列? if to_send.get("attributes") or to_send.get("telemetry"): self.__gateway.send_to_storage(self.get_name(), to_send) self.statistics['MessagesSent'] += 1 except ConnectionException: time.sleep(5) log.error("Connection lost! Reconnecting...") except Exception as e: log.exception(e)
def convert(self, config, data): self.__result["telemetry"] = [] self.__result["attributes"] = [] for config_data in data: if self.__result.get(config_data) is None: self.__result[config_data] = [] for tag in data[config_data]: log.debug(tag) data_sent = data[config_data][tag]["data_sent"] input_data = data[config_data][tag]["input_data"] log.debug("Called convert function from %s with args", self.__class__.__name__) log.debug(data_sent) log.debug(input_data) result = None if data_sent.get("functionCode") == 1 or data_sent.get("functionCode") == 2: result = input_data.bits log.debug(result) if "registerCount" in data_sent: result = result[:data_sent["registerCount"]] else: result = result[0] elif data_sent.get("functionCode") == 3 or data_sent.get("functionCode") == 4: result = input_data.registers byte_order = data_sent.get("byteOrder", "LITTLE") reg_count = data_sent.get("registerCount", 1) type_of_data = data_sent["type"] try: try: if byte_order == "LITTLE": decoder = BinaryPayloadDecoder.fromRegisters(result, byteorder=Endian.Little) elif byte_order == "BIG": decoder = BinaryPayloadDecoder.fromRegisters(result, byteorder=Endian.Big) except TypeError: if byte_order == "LITTLE": decoder = BinaryPayloadDecoder.fromRegisters(result, endian=Endian.Little) elif byte_order == "BIG": decoder = BinaryPayloadDecoder.fromRegisters(result, endian=Endian.Big) else: log.warning("byte order is not BIG or LITTLE") # continue except Exception as e: log.error(e) if type_of_data == "string": result = decoder.decode_string(2 * reg_count) elif type_of_data == "long": try: if reg_count == 1: # r = decoder.decode_8bit_int() result = decoder.decode_16bit_int() elif reg_count == 2: result = decoder.decode_32bit_int() elif reg_count == 4: result = decoder.decode_64bit_int() else: log.warning("unsupported register count for long data type in response for tag %s", data_sent["tag"]) except Exception as e: log.exception(e) elif type_of_data == "double": if reg_count == 2: result = decoder.decode_32bit_float() elif reg_count == 4: result = decoder.decode_64bit_float() else: log.warning("unsupported register count for double data type in response for tag %s", data_sent["tag"]) # continue elif type_of_data == "bit": if "bit" in data_sent: if type(result) == list: if len(result) > 1: log.warning("with bit parameter only one register is expected, got more then one in response for tag %s", data_sent["tag"]) continue result = result[0] position = 15 - data_sent["bit"] # reverse order # transform result to string representation of a bit sequence, add "0" to make it longer >16 result = "0000000000000000" + str(bin(result)[2:]) # get length of 16, then get bit, then cast it to int(0||1 from "0"||"1", then cast to boolean) result = bool(int((result[len(result) - 16:])[15 - position])) else: log.error("Bit address not found in config for modbus connector for tag: %s", data_sent["tag"]) else: log.warning("unknown data type, not string, long or double in response for tag %s", data_sent["tag"]) continue try: if result == 0: self.__result[config_data].append({tag: result}) elif int(result): self.__result[config_data].append({tag: result}) except ValueError: try: self.__result[config_data].append({tag: int(result, 16)}) except ValueError: self.__result[config_data].append({tag: result.decode('UFT-8')}) self.__result["telemetry"] = self.__result.pop("timeseries") log.debug(self.__result) return self.__result