diff --git a/thingsboard_gateway/connectors/bacnet/application.py b/thingsboard_gateway/connectors/bacnet/application.py index c6515c4f7..aacd2eb73 100644 --- a/thingsboard_gateway/connectors/bacnet/application.py +++ b/thingsboard_gateway/connectors/bacnet/application.py @@ -237,7 +237,7 @@ def __get_read_access_specifications(self, object_list, vendor_id): object_id = object['objectId'] if not isinstance(object_id, ObjectIdentifier): obj_str = f"{object['objectType']},{object_id}" - object_id = ObjectIdentifier(obj_str) + object_id = vendor_info.object_identifier(obj_str) object_class = vendor_info.get_object_class(object_id[0]) if object_class is None: @@ -249,7 +249,7 @@ def __get_read_access_specifications(self, object_list, vendor_id): object['propertyId'] = {object['propertyId']} for prop in object['propertyId']: - property_identifier = PropertyIdentifier(prop) + property_identifier = vendor_info.property_identifier(prop) properties.append(property_identifier) @@ -324,12 +324,12 @@ def decode_tag_list(self, tag_list, vendor_id): result_list = [] for read_access_result in tag_list.listOfReadAccessResults: - object_identifier = read_access_result.objectIdentifier + object_identifier = vendor_info.object_identifier(read_access_result.objectIdentifier) object_class = vendor_info.get_object_class(object_identifier[0]) for read_access_result_element in read_access_result.listOfResults: try: - property_identifier = read_access_result_element.propertyIdentifier + property_identifier = vendor_info.property_identifier(read_access_result_element.propertyIdentifier) property_array_index = read_access_result_element.propertyArrayIndex read_result = read_access_result_element.readResult diff --git a/thingsboard_gateway/connectors/bacnet/bacnet_connector.py b/thingsboard_gateway/connectors/bacnet/bacnet_connector.py index 87ab7c05b..097b6c5db 100644 --- a/thingsboard_gateway/connectors/bacnet/bacnet_connector.py +++ b/thingsboard_gateway/connectors/bacnet/bacnet_connector.py @@ -28,6 +28,7 @@ from thingsboard_gateway.gateway.statistics.statistics_service import StatisticsService from thingsboard_gateway.tb_utility.tb_logger import init_logger from thingsboard_gateway.tb_utility.tb_utility import TBUtility +from thingsboard_gateway.tb_utility.tb_loader import TBModuleLoader try: from bacpypes3.apdu import ErrorRejectAbortNack @@ -73,6 +74,9 @@ def __init__(self, gateway, config, connector_type): self.__parse_ede_config() self.__log.debug('EDE config parsed') + # importing the proprietary package registers all available custom object types and properties + TBModuleLoader.import_package_files(connector_type, "proprietary") + if BackwardCompatibilityAdapter.is_old_config(config): backward_compatibility_adapter = BackwardCompatibilityAdapter(config, self.__log) self.__config = backward_compatibility_adapter.convert() diff --git a/thingsboard_gateway/connectors/bacnet/bacnet_uplink_converter.py b/thingsboard_gateway/connectors/bacnet/bacnet_uplink_converter.py index 339ea4b9e..fe6b0158a 100644 --- a/thingsboard_gateway/connectors/bacnet/bacnet_uplink_converter.py +++ b/thingsboard_gateway/connectors/bacnet/bacnet_uplink_converter.py @@ -47,16 +47,16 @@ def convert(self, config, data): converted_values = self.__convert_data(values_group) if len(converted_values) > 0: - data_key, unsed_values = self.__get_data_key_name(item_config['key'], converted_values) + data_key, unused_values = self.__get_data_key_name(item_config['key'], converted_values) - if len(unsed_values) == 1: + if len(unused_values) == 1: datapoint_key = TBUtility.convert_key_to_datapoint_key(data_key, device_report_strategy, item_config, self.__log) - converted_data_append_methods[item_config['type']]({datapoint_key: round(unsed_values[0]['value'], 2) if isinstance(unsed_values[0]['value'], float) else str(unsed_values[0]['value'])}) # noqa + converted_data_append_methods[item_config['type']]({datapoint_key: round(unused_values[0]['value'], 2) if isinstance(unused_values[0]['value'], float) else str(unused_values[0]['value'])}) # noqa else: - for item in unsed_values: + for item in unused_values: datapoint_key = TBUtility.convert_key_to_datapoint_key(f'{data_key}.{item["propName"]}', device_report_strategy, item_config, diff --git a/thingsboard_gateway/extensions/bacnet/proprietary/desigo_cc.py b/thingsboard_gateway/extensions/bacnet/proprietary/desigo_cc.py new file mode 100644 index 000000000..65ff1d0d1 --- /dev/null +++ b/thingsboard_gateway/extensions/bacnet/proprietary/desigo_cc.py @@ -0,0 +1,78 @@ +""" +# Siemens Desigo CC +# Register proprietary Objects and Properties +""" + +from bacpypes3.constructeddata import ArrayOf +from bacpypes3.vendor import VendorInfo +from bacpypes3.basetypes import PropertyIdentifier +from bacpypes3.object import AnalogInputObject as _AnalogInputObject +from bacpypes3.object import PulseConverterObject as _PulseConverterObject +from bacpypes3.object import AnalogValueObject as _AnalogValueObject +from bacpypes3.object import DeviceObject as _DeviceObject +from bacpypes3.object import NetworkPortObject as _NetworkPortObject +from bacpypes3.primitivedata import ( + ObjectType, + CharacterString, +) + + +# this vendor identifier reference is used when registering custom classes +_vendor_id = 7 +_vendor_name = "Siemens Building Technologies" + + +class ProprietaryObjectType(ObjectType): + """ + This is a list of the object type enumerations for proprietary object types, + see Clause 23.4.1. + """ + + pass + + +class ProprietaryPropertyIdentifier(PropertyIdentifier): + """ + This is a list of the property identifiers that are used in custom object + types or are used in custom properties of standard types. + """ + + descriptionList = 3121 + + +# create a VendorInfo object for this custom application before registering +# specialize object classes +_desigo_cc = VendorInfo( + _vendor_id, ProprietaryObjectType, ProprietaryPropertyIdentifier +) + + +class DesigoCCDeviceObject(_DeviceObject): + """ + When running as an instance of this custom device, the DeviceObject is + an extension of the one defined in bacpypes3.device + """ + + descriptionList: CharacterString + + +class NetworkPortObject(_NetworkPortObject): + """ + When running as an instance of this custom device, the NetworkPortObject is + an extension of the one defined in bacpypes3.networkport (in this + case doesn't add any proprietary properties). + """ + + pass + + +class DesigoCCAnalogInputObject(_AnalogInputObject): + descriptionList: ArrayOf(CharacterString) + + +class DesigoCCPulseConverterObject(_PulseConverterObject): + descriptionList: ArrayOf(CharacterString) + + +class DesigoCCAnalogValueObject(_AnalogValueObject): + descriptionList: ArrayOf(CharacterString) diff --git a/thingsboard_gateway/tb_utility/tb_loader.py b/thingsboard_gateway/tb_utility/tb_loader.py index 553b6e409..5acab1100 100644 --- a/thingsboard_gateway/tb_utility/tb_loader.py +++ b/thingsboard_gateway/tb_utility/tb_loader.py @@ -14,6 +14,7 @@ # from importlib.util import module_from_spec, spec_from_file_location +from importlib import import_module from inspect import getmembers, isclass from logging import getLogger, setLoggerClass from os import listdir, path @@ -81,3 +82,31 @@ def import_module(extension_type, module_name): log.error("Error while importing module %s from %s.", module_name, current_extension_path, exc_info=e) errors.append(e) return errors + + @staticmethod + def import_package_files(extension_type, package_name): + errors = [] + if len(TBModuleLoader.PATHS) == 0: + TBModuleLoader.find_paths() + try: + for current_path in TBModuleLoader.PATHS: + current_extension_path = current_path + path.sep + extension_type + package_path = current_extension_path + path.sep + package_name + if path.exists(package_path): + for file in listdir(package_path): + if not file.startswith("__") and (file.endswith(".py") or file.endswith(".pyc")): + try: + module_name, _ = path.splitext(file) + module = f'{package_path.replace("/", ".")}.{module_name}' + if module.startswith("."): + module = module[1:] + log.info("Import %s from %s.", module, package_path) + import_module(module) + except ImportError as e: + log.info(e.msg) + errors.append(e.msg) + continue + except Exception as e: + log.error("Error while importing package %s from %s.", package_name, package_path, exc_info=e) + errors.append(e) + return errors