diff --git a/.gitignore b/.gitignore index 178135c..b1113da 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ /dist/ +__pycache__/ +~$* +MTPPy.egg-info/ \ No newline at end of file diff --git a/examples/data_objects.py b/examples/data_objects.py new file mode 100644 index 0000000..27dafd8 --- /dev/null +++ b/examples/data_objects.py @@ -0,0 +1,270 @@ +from opcua import Client + +from mtppy.opcua_server_pea import OPCUAServerPEA + +from mtppy.indicator_elements import * +from mtppy.operation_elements import * +from mtppy.active_elements import * +from mtppy.suc_data_assembly import SUCDataAssembly +from mtppy.attribute import Attribute + +import time + +logging.basicConfig( + format='%(asctime)s.%(msecs)03d; [%(levelname)s]; ' + '%(module)s.%(funcName)s: %(message)s', + datefmt='%H:%M:%S', level=logging.INFO) +logging.getLogger("opcua").setLevel(logging.ERROR) +logging.getLogger("mtppy.indicator_elements").setLevel(logging.DEBUG) +logging.getLogger("mtppy.operation_elements").setLevel(logging.DEBUG) +logging.getLogger("mtppy.active_elements").setLevel(logging.DEBUG) + +_log = logging.getLogger(__name__) + + +module = OPCUAServerPEA(endpoint="opc.tcp://127.0.0.1:4840/") + +### Indicator elements ### +bin_view = BinView('indicator_bin', 'demo object', v_state_0='Off', v_state_1='On') +dint_view = DIntView('indicator_dint', 'demo object', v_scl_min=0, v_scl_max=100, v_unit=1342) +ana_view = AnaView('indicator_ana', 'demo object', v_scl_min=0, v_scl_max=100, v_unit=1342) +str_view = StringView('indicator_str', 'demo object') + +# add indicator elements to the module +module.add_indicator_element(bin_view) +module.add_indicator_element(dint_view) +module.add_indicator_element(ana_view) +module.add_indicator_element(str_view) + + +### Operation elements ### +bin_man = BinMan('op_bin', 'demo object', v_state_0='Off', v_state_1='On', init_value=0) +dint_man = DIntMan('op_dint', 'demo object', v_min=0, v_max=100, + v_scl_min=0, v_scl_max=100, v_unit=1342, init_value=0) +ana_man = AnaMan('op_ana', 'demo object', v_min=0, v_max=100, + v_scl_min=0, v_scl_max=100, v_unit=1342, init_value=0) + +# add operation elements to the module +module.add_operation_element(bin_man) +module.add_operation_element(dint_man) +module.add_operation_element(ana_man) + + +## Internal operation elements ## +bin_man_int = BinManInt('op_bin_int', 'demo object', v_state_0='Off', v_state_1='On', init_value=0) +dint_man_int = DIntManInt('op_dint_int', 'demo object', v_min=0, v_max=100, + v_scl_min=0, v_scl_max=100, v_unit=1342, init_value=0) +ana_man_int = AnaManInt('op_ana_int', 'demo object', v_min=0, v_max=100, + v_scl_min=0, v_scl_max=100, v_unit=1342, init_value=0) + +# add internal operation elements to the module +module.add_operation_element(bin_man_int) +module.add_operation_element(dint_man_int) +module.add_operation_element(ana_man_int) + + +### Active elements ### +bin_vlv = BinVlv('active_bin', 'demo object', safe_pos=0, safe_pos_en=True, + perm_en=True, intl_en=True, prot_en=True) + +# add active elements to the module +module.add_active_element(bin_vlv) + + +### Custom Data Assembly ### +# it is also possible to create custom data assemblies by inheriting from SUCDataAssembly +# Attributes can be using the self._add_attribute() function inside the __init__ function +class CustomDataAssembly(SUCDataAssembly): + def __init__(self, tag_name: str, tag_description: str = '', pol_request: int = 0): + super().__init__(tag_name, tag_description) + + self._add_attribute(Attribute('POLRequest', int, + init_value=pol_request, sub_cb=self._set_pol_request)) + self._add_attribute(Attribute('PEAResponse', str, init_value='Hello World')) + + self.pol_request = pol_request + + def set_pea_response(self, value: str): + self.attributes['PEAResponse'].set_value(value) + _log.info(f'PEAResponse set to {value}') + + def get_pol_request(self) -> int: + return self.pol_request + + def _set_pol_request(self, value: int): + # ignore reset callback + if value == 0: + return + self.pol_request = value + _log.info(f'POLRequest set to {value} ') + + if value == 42: + self.set_pea_response('The answer to life, the universe and everything.') + else: + self.set_pea_response(f'POLRequest was {value}.') + + self.attributes['POLRequest'].set_value(0) # reset POLRequest after reading + + +custom_da = CustomDataAssembly('custom_da_1', 'demo object', pol_request=123) +module.add_custom_data_assembly(custom_da, "custom_data_assemblies") + +# start the OPC UA server +module.run_opcua_server() + +# while True: +# time.sleep(1) + +############################################################################################### +# Test +opcua_client = Client(module.endpoint) +opcua_client.connect() +time.sleep(1) + + +print('--- Set values of indicator elements ---') +bin_view.set_v(1) +dint_view.set_v(55) +ana_view.set_v(33.3) +str_view.set_v('Hello MTPPy!') +time.sleep(1) + +print('--- Print values of indicator elements (Simulating HMI side) ---') +print(f"bin_view: {opcua_client.get_node('ns=3;s=indicator_elements.indicator_bin.V').get_value()}") +print(f"dint_view: {opcua_client.get_node('ns=3;s=indicator_elements.indicator_dint.V').get_value()}") +print(f"ana_view: {opcua_client.get_node('ns=3;s=indicator_elements.indicator_ana.V').get_value()}") +print(f"str_view: {opcua_client.get_node('ns=3;s=indicator_elements.indicator_str.V').get_value()}") +time.sleep(2) + + +print() +print('--- Set values of operation elements (Simulating HMI side) ---') +opcua_client.get_node('ns=3;s=operation_elements.op_bin.VMan').set_value(1) +opcua_client.get_node('ns=3;s=operation_elements.op_dint.VMan').set_value(77) +opcua_client.get_node('ns=3;s=operation_elements.op_ana.VMan').set_value(44.4) +time.sleep(1) + +print('--- Print values of operation elements ---') +print(f"bin_man: {bin_man.get_v_out()}") +print(f"dint_man: {dint_man.get_v_out()}") +print(f"ana_man: {ana_man.get_v_out()}") +time.sleep(2) + + +print() +print('--- Set values of internal operation elements ---') +bin_man_int.set_v_int(1) +dint_man_int.set_v_int(88) +ana_man_int.set_v_int(55.5) +time.sleep(1) + +print('--- Print values of internal operation elements ---') +print( + f"bin_man_int: {opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.VOut').get_value()}") +print( + f"dint_man_int: {opcua_client.get_node('ns=3;s=operation_elements.op_dint_int.VOut').get_value()}") +print( + f"ana_man_int: {opcua_client.get_node('ns=3;s=operation_elements.op_ana_int.VOut').get_value()}") +time.sleep(2) + +print('--- Set SrcChannel to 0 (operator switches)') +opcua_client.get_node( + 'ns=3;s=operation_elements.op_bin_int.op_src_mode.SrcChannel').set_value(False) +print('--- Set to Manual via operator ---') +opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.op_src_mode.SrcManOp').set_value(True) +print('--- Set value via operator ---') +opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.VMan').set_value(0) +time.sleep(1) +print(f"bin_man_int: {bin_man_int.get_v_out()}") +time.sleep(1) + +print('--- Set SrcChannel to 1 (internal control) ---') +opcua_client.get_node( + 'ns=3;s=operation_elements.op_bin_int.op_src_mode.SrcChannel').set_value(True) +print('--- Set to Automatic via internal control ---') +opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.op_src_mode.SrcIntAut').set_value(True) +print('--- Set value via internal control ---') +bin_man_int.set_v_int(1) +time.sleep(1) +print( + f"bin_man_int: {opcua_client.get_node('ns=3;s=operation_elements.op_bin_int.VOut').get_value()}") +time.sleep(2) + + +print() +print('--- Set values of active element ---') +bin_vlv.set_open_aut(True) +time.sleep(1) +print( + f"bin_vlv position: {opcua_client.get_node('ns=3;s=active_elements.active_bin.Ctrl').get_value()}") +time.sleep(1) +bin_vlv.set_close_aut(True) +time.sleep(1) +print( + f"bin_vlv position: {opcua_client.get_node('ns=3;s=active_elements.active_bin.Ctrl').get_value()}") +time.sleep(2) + +print('--- Demonstrate bin_vlv locks ---') +locks = bin_vlv.locks + +print(f"Initial permit status: {locks.permit_status()}") +print(f"Initial interlock status: {locks.interlock_status()}") +print(f"Initial protect status: {locks.protect_status()}") + +# Enable and disable permit +locks.set_permit(False) +print(f"Permit status after disabling: {locks.permit_status()}") +bin_vlv.set_open_aut(True) +time.sleep(1) +print( + f"bin_vlv position (should not change): {opcua_client.get_node('ns=3;s=active_elements.active_bin.Ctrl').get_value()}") +time.sleep(1) +locks.set_permit(True) +print(f"Permit status after enabling: {locks.permit_status()}") +time.sleep(1) +bin_vlv.set_open_aut(True) +time.sleep(1) +print( + f"bin_vlv position (should change now): {opcua_client.get_node('ns=3;s=active_elements.active_bin.Ctrl').get_value()}") +time.sleep(2) + +# Enable and disable interlock +locks.set_interlock(True) +print(f"Interlock status after enabling: {locks.interlock_status()}") +sleep(1) +print(f"Interlock triggered, valve should close automatically: valve status: {bin_vlv.get_ctrl()}") +locks.set_interlock(False) +print(f"Interlock status after disabling: {locks.interlock_status()}") + +# Enable and disable protect +locks.set_protect(True) +print(f"Protect status after enabling: {locks.protect_status()}") +bin_vlv.reset_vlv() +print(f"Protect status after disabling: {locks.protect_status()}") +time.sleep(2) + + +print() +print('--- Set and read Custom Data Assembly attributes ---') +print("--- Set POLRequest to 42 (Simulating HMI side) ---") +opcua_client.get_node('ns=3;s=custom_data_assemblies.custom_da_1.POLRequest').set_value(42) +time.sleep(1) +print("--- Read POLRequest and PEAResponse attributes ---") +print(f"POLRequest: {custom_da.get_pol_request()}") +print( + f"PEAResponse: {opcua_client.get_node('ns=3;s=custom_data_assemblies.custom_da_1.PEAResponse').get_value()}") +time.sleep(1) +print("--- Try setting the PEAResponse attribute from HMI side (should have no effect) ---") +try: + opcua_client.get_node('ns=3;s=custom_data_assemblies.custom_da_1.PEAResponse').set_value( + 'PEAResponse set from from HMI') +except Exception as e: + print(f"Caught exception as expected: {e}") +time.sleep(1) +print( + f"PEAResponse on server (should not be changed): {opcua_client.get_node('ns=3;s=custom_data_assemblies.custom_da_1.PEAResponse').get_value()}") +time.sleep(2) + +print('--- Demo complete ---') +opcua_client.disconnect() +module.get_opcua_server().stop() diff --git a/examples/virtual_pea_exception_handling.py b/examples/virtual_pea_exception_handling.py new file mode 100644 index 0000000..01a8797 --- /dev/null +++ b/examples/virtual_pea_exception_handling.py @@ -0,0 +1,159 @@ +import time +import random +import logging + +from opcua import Client + +from mtppy.opcua_server_pea import OPCUAServerPEA +from mtppy.service import Service +from mtppy.procedure import Procedure +from mtppy.indicator_elements import * + +from mtppy.command_codes import CommandCodes +from mtppy.state_codes import StateCodes + +CommandCodes = CommandCodes() +StateCodes = StateCodes() + +logging.basicConfig( + format='%(asctime)s.%(msecs)03d; [%(levelname)s];' + '%(module)s.%(funcName)s: %(message)s', + datefmt='%H:%M:%S', level=logging.INFO) +logging.getLogger("opcua").setLevel(logging.ERROR) +logging.getLogger("mtppy.service").setLevel(logging.DEBUG) + +_log = logging.getLogger(__name__) + + +class ErrorException(Exception): + pass + + +class WarningException(Exception): + pass + + +class RandomNumberGenerator(Service): + def __init__(self, tag_name, tag_description): + super().__init__(tag_name, tag_description, self.exception_callback) + + # Procedure definition + proc_1 = Procedure(1, 'default', is_self_completing=False, is_default=True) + + # Adding procedure report value + proc_1.add_report_value( + AnaView('generated_value', v_scl_min=0, v_scl_max=100, v_unit=23), + ) + + # Allocating procedure to the service + self.add_procedure(proc_1) + + self.enable_hold_loop() + + self.cycle_count = 0 + + def starting(self): + _log.info('- Starting -') + _log.info('Applying procedure parameters...') + self.cycle_count = 0 + + def execute(self): + _log.info('- Execute -') + lower_bound = 0 + upper_bound = 10 + while self.is_state('execute'): + _log.info(f'Execute cycle {self.cycle_count}') + + # Execute random number generation + generated_number = random.randint(lower_bound, upper_bound) + + # Return report value + self.procedures[1].report_values['generated_value'].set_v(generated_number) + if generated_number == 5: + raise WarningException('This is a warning exception for testing.') + if generated_number == 8: + raise ErrorException('This is an error exception for testing.') + self.cycle_count += 1 + time.sleep(0.1) + + def completing(self): + _log.info('- Completing -') + + def exception_callback(self, exception: Exception): + if isinstance(exception, WarningException): + _log.warning(f'Warning exception handled in exception_callback: {str(exception)}') + # handle warning exception (e.g., set service to hold state) + self.state_machine.hold() + elif isinstance(exception, ErrorException): + _log.error(f'Error exception handled in exception_callback: {str(exception)}') + # handle error exception (e.g., set service to aborted state) + self.state_machine.abort() + else: + _log.error(f'Unknown exception handled in exception_callback: {str(exception)}') + self.state_machine.abort() + + # starting, execute, completing methods have to be defined for each service + # other state methods (see service documentation) can be overridden as needed, + # by default they only log state entries + + +if __name__ == '__main__': + + module = OPCUAServerPEA() + + # Service definition + service_1 = RandomNumberGenerator('rand_num_gen', 'This services generates random number') + module.add_service(service_1) + + # Start server + print('--- Start OPC UA server ---') + module.run_opcua_server() + + ############################################################################################### + # Test + opcua_client = Client(module.endpoint) + opcua_client.connect() + time.sleep(1) + + print('--- Set service to Operator mode ---') + opcua_client.get_node('ns=3;s=services.rand_num_gen.op_src_mode.StateOpOp').set_value(True) + time.sleep(1) + + print('--- Start service ---') + opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value( + CommandCodes.start) + + cycle = 0 + while cycle < 12: + if opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.StateCur').get_value() == StateCodes.held: + print(f'--- Unholding service at cycle ---') + opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value( + CommandCodes.unhold) + if opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.StateCur').get_value() == StateCodes.aborted: + print(f'--- Resetting service at cycle ---') + opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value( + CommandCodes.reset) + break + + time.sleep(1) + cycle += 1 + + if cycle >= 12: + print('--- Complete service ---') + opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value( + CommandCodes.complete) + time.sleep(1) + + print('--- Reset service ---') + opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value( + CommandCodes.reset) + time.sleep(1) + + time.sleep(1) + print('--- Set service to Offline mode ---') + opcua_client.get_node('ns=3;s=services.rand_num_gen.op_src_mode.StateOffOp').set_value(True) + time.sleep(1) + + print('--- Demo complete ---') + opcua_client.disconnect() + module.get_opcua_server().stop() diff --git a/examples/virtual_pea_minimal.py b/examples/virtual_pea_minimal.py index 8e9f797..e899cca 100644 --- a/examples/virtual_pea_minimal.py +++ b/examples/virtual_pea_minimal.py @@ -1,17 +1,32 @@ +import time +import random +from datetime import datetime +import logging + +from opcua import Client + from mtppy.opcua_server_pea import OPCUAServerPEA from mtppy.mtp_generator import MTPGenerator from mtppy.service import Service from mtppy.procedure import Procedure -from mtppy.operation_elements import * +from mtppy.parameter_elements import * from mtppy.indicator_elements import * from mtppy.active_elements import * -import time -import random -from datetime import datetime +from mtppy.command_codes import CommandCodes +from mtppy.state_codes import StateCodes + +CommandCodes = CommandCodes() +StateCodes = StateCodes() -logging.basicConfig(format='%(asctime)s.%(msecs)03d [%(levelname)s] %(module)s.%(funcName)s: %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO) +logging.basicConfig( + format='%(asctime)s.%(msecs)03d; [%(levelname)s];' + '%(module)s.%(funcName)s: %(message)s', + datefmt='%H:%M:%S', level=logging.INFO) +logging.getLogger("opcua").setLevel(logging.ERROR) +logging.getLogger("mtppy.service").setLevel(logging.DEBUG) + +_log = logging.getLogger(__name__) class RandomNumberGenerator(Service): @@ -19,94 +34,55 @@ def __init__(self, tag_name, tag_description): super().__init__(tag_name, tag_description) # Procedure definition - proc_0 = Procedure(0, 'cont', is_self_completing=False, is_default=True) + proc_1 = Procedure(1, 'cont', is_self_completing=False, is_default=True) # Adding two procedure parameters - proc_0.add_procedure_parameter( + proc_1.add_procedure_parameter( DIntServParam('lower_bound', v_min=0, v_max=100, v_scl_min=0, v_scl_max=100, v_unit=23)) - proc_0.add_procedure_parameter( + proc_1.add_procedure_parameter( DIntServParam('upper_bound', v_min=0, v_max=100, v_scl_min=0, v_scl_max=100, v_unit=23)) + # optional: link op_src_mode of procedure parameters to service op_src_mode + for parameter in proc_1.procedure_parameters.values(): + self.op_src_mode.add_linked_op_src_mode(parameter.op_src_mode) + # Adding procedure report value - proc_0.add_report_value( + proc_1.add_report_value( AnaView('generated_value', v_scl_min=0, v_scl_max=100, v_unit=23), ) # Allocating procedure to the service - self.add_procedure(proc_0) - - def idle(self): - print('- Idle -') - cycle = 0 - while self.is_state('idle'): - print(f'Idle cycle {cycle}') - print('Doing nothing...') - cycle += 1 - time.sleep(1) + self.add_procedure(proc_1) def starting(self): - print('- Starting -') - print('Applying procedure parameters...') - self.state_change() + _log.info('- Starting -') + _log.info('Applying procedure parameters...') def execute(self): - print('- Execute -') + _log.info('- Execute -') cycle = 0 while self.is_state('execute'): - print('Execute cycle %i' % cycle) + _log.info('Execute cycle %i' % cycle) # Read procedure parameters - lower_bound = self.procedures[0].procedure_parameters['lower_bound'].get_v_out() - upper_bound = self.procedures[0].procedure_parameters['upper_bound'].get_v_out() + lower_bound = self.procedures[1].procedure_parameters['lower_bound'].get_v_out() + upper_bound = self.procedures[1].procedure_parameters['upper_bound'].get_v_out() # Execute random number generation generated_number = random.randint(lower_bound, upper_bound) # Return report value - self.procedures[0].report_values['generated_value'].set_v(generated_number) + self.procedures[1].report_values['generated_value'].set_v(generated_number) cycle += 1 time.sleep(0.1) def completing(self): - self.state_change() + _log.info('- Completing -') - def completed(self): - pass - - def pausing(self): - pass - - def paused(self): - pass - - def resuming(self): - pass - - def holding(self): - pass - - def held(self): - pass - - def unholding(self): - pass - - def stopping(self): - pass - - def stopped(self): - pass - - def aborting(self): - pass - - def aborted(self): - pass - - def resetting(self): - print('- Resetting -') - self.state_change() + # starting, execute, completing methods have to be defined for each service + # other state methods (see service documentation) can be overridden as needed, + # by default they only log state entries if __name__ == '__main__': @@ -119,7 +95,7 @@ def resetting(self): manifest_template_path = '../manifest_files/manifest_template.xml' mtp_generator = MTPGenerator(writer_info_dict, export_manifest_path, manifest_template_path) - module = OPCUAServerPEA(mtp_generator) + module = OPCUAServerPEA(mtp_generator, endpoint="opc.tcp://127.0.0.1:4840/") # Service definition service_1 = RandomNumberGenerator('rand_num_gen', 'This services generates random number') @@ -133,36 +109,49 @@ def resetting(self): print('--- Start OPC UA server ---') module.run_opcua_server() + ############################################################################################### # Test - opcua_server = module.get_opcua_server() - opcua_ns = module.get_opcua_ns() + opcua_client = Client(module.endpoint) + opcua_client.connect() time.sleep(1) print('--- Set procedure parameters to Operator mode ---') - opcua_server.get_node('ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOpOp').set_value(True) - opcua_server.get_node('ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOpOp').set_value(True) + opcua_client.get_node( + 'ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOpOp').set_value(True) + opcua_client.get_node( + 'ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOpOp').set_value(True) time.sleep(1) print('--- Set procedure parameter values ---') - opcua_server.get_node('ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VOp').set_value(40) - opcua_server.get_node('ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VOp').set_value(60) + opcua_client.get_node( + 'ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VOp').set_value(40) + opcua_client.get_node( + 'ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VOp').set_value(60) time.sleep(1) print('--- Set service to Operator mode ---') - opcua_server.get_node('ns=3;s=services.rand_num_gen.op_src_mode.StateOpOp').set_value(True) + opcua_client.get_node('ns=3;s=services.rand_num_gen.op_src_mode.StateOpOp').set_value(True) time.sleep(1) print('--- Start service ---') - opcua_server.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value(4) + opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value( + CommandCodes.start) time.sleep(10) print('--- Complete service ---') - opcua_server.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value(1024) + opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value( + CommandCodes.complete) time.sleep(1) print('--- Reset service ---') - opcua_server.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value(2) + opcua_client.get_node('ns=3;s=services.rand_num_gen.state_machine.CommandOp').set_value( + CommandCodes.reset) + time.sleep(1) - print('--- Set service dummy to Offline mode ---') - opcua_server.get_node('ns=3;s=services.rand_num_gen.op_src_mode.StateOffOp').set_value(True) + print('--- Set service to Offline mode ---') + opcua_client.get_node('ns=3;s=services.rand_num_gen.op_src_mode.StateOffOp').set_value(True) time.sleep(1) + + print('--- Demo complete ---') + opcua_client.disconnect() + module.get_opcua_server().stop() diff --git a/examples/virtual_pea_with_events.py b/examples/virtual_pea_with_events.py new file mode 100644 index 0000000..f58e50d --- /dev/null +++ b/examples/virtual_pea_with_events.py @@ -0,0 +1,205 @@ +import time +import threading +import logging + +from opcua import Client + +from mtppy.opcua_server_pea import OPCUAServerPEA +from mtppy.service import Service +from mtppy.procedure import Procedure +from mtppy.parameter_elements import * +from mtppy.indicator_elements import * +from mtppy.active_elements import * + +from mtppy.command_codes import CommandCodes +from mtppy.state_codes import StateCodes + +CommandCodes = CommandCodes() +StateCodes = StateCodes() + +logging.basicConfig( + format='%(asctime)s.%(msecs)03d; [%(levelname)s]; ' + '%(module)s.%(funcName)s: %(message)s', + datefmt='%H:%M:%S', level=logging.INFO) +logging.getLogger("opcua").setLevel(logging.ERROR) +logging.getLogger("mtppy.service").setLevel(logging.DEBUG) + +_log = logging.getLogger(__name__) + +external_event = threading.Event() + +################################################################################################### +# Function to wait for two events at the same time +# !!! This modifies the standard threading.Event, could break if it would be changed in the library !!! + + +def _or_set(self): + # calls the original set method and then calls the changed callback + self._set() + self.changed() + + +def _or_clear(self): + # calls the original clear method and then calls the changed callback + self._clear() + self.changed() + + +def _orify(e: threading.Event, changed_callback): + # save original methods to also be called when set/clear is called + e._set = e.set + e._clear = e.clear + # add changed callback, which is called when the event is set or cleared + e.changed = changed_callback + # redefine set and clear methods to call modified methods that also call changed + e.set = lambda: _or_set(e) + e.clear = lambda: _or_clear(e) + + +def OrEvent(*events: threading.Event): + or_event = threading.Event() + + # check if any of the events are the same + if len(events) != len(set(events)): + raise ValueError("Events must be unique.") + + # check if any of the events are already orified + for e in events: + if hasattr(e, 'changed'): + raise ValueError("Events must not be orified already.") + + def changed(): + bools = [e.is_set() for e in events] + if any(bools): + or_event.set() + else: + or_event.clear() + + # Make every event able to call the changed function + # when it is set or cleared. + for e in events: + _orify(e, changed) + changed() + return or_event + + +def wait_for_either(e1, e2, timeout: float = None) -> bool: + """ + Waits for either of the two events to be set. + Args: + e1 (threading.Event): First event to wait for. + e2 (threading.Event): Second event to wait for. + timeout (float): Maximum time to wait in seconds. If None, waits indefinitely. + + Returns: + bool: True if either event is set, False if timeout occurs. + """ + return OrEvent(e1, e2).wait(timeout=timeout) +################################################################################################### + + +class WaitForExternalEvent(Service): + def __init__(self, tag_name, tag_description): + super().__init__(tag_name, tag_description) + + # Procedure definition + proc_1 = Procedure(1, 'default', is_self_completing=True, is_default=True) + + # Adding procedure report value + proc_1.add_report_value( + BinView('event_status', v_state_0='no', v_state_1='yes'), + ) + + # Allocating procedure to the service + self.add_procedure(proc_1) + + def starting(self): + _log.info('- Starting -') + _log.info('Creating external event...') + + global external_event + external_event = threading.Event() + + def execute(self): + _log.info('- Execute -') + + global external_event + + # This will wait until either the external event is set + # or the service is requested to stop. + wait_for_either(external_event, self.get_state_stop_event()) + + if external_event.is_set(): + _log.info('External event detected!') + self.get_current_procedure().report_values['event_status'].set_v(1) + else: + _log.info('Service stop requested before external event.') + self.get_current_procedure().report_values['event_status'].set_v(0) + + def completing(self): + _log.info('- Completing -') + + # starting, execute, completing methods have to be defined for each service + # other state methods (see service documentation) can be overridden as needed, + # by default they only log state entries + + +if __name__ == '__main__': + module = OPCUAServerPEA(endpoint="opc.tcp://127.0.0.1:4840/") + + # Service definition + service = WaitForExternalEvent('WaitForEvent', + 'Service that waits for an external event') + module.add_service(service) + + # Start server + print('--- Start OPC UA server ---') + module.run_opcua_server() + + ############################################################################################### + # Test + opcua_client = Client(module.endpoint) + opcua_client.connect() + time.sleep(1) + + print('--- Set service to Operator mode ---') + opcua_client.get_node('ns=3;s=services.WaitForEvent.op_src_mode.StateOpOp').set_value(True) + time.sleep(1) + + print('--- Start service ---') + opcua_client.get_node('ns=3;s=services.WaitForEvent.state_machine.CommandOp').set_value( + CommandCodes.start) + time.sleep(4) + + print('--- Trigger external event ---') + external_event.set() + time.sleep(2) + + print('--- Reset service ---') + opcua_client.get_node('ns=3;s=services.WaitForEvent.state_machine.CommandOp').set_value( + CommandCodes.reset) + external_event.clear() + time.sleep(1) + + print('--- Start service ---') + opcua_client.get_node('ns=3;s=services.WaitForEvent.state_machine.CommandOp').set_value( + CommandCodes.start) + time.sleep(4) + + print('--- Stop service before external event ---') + opcua_client.get_node('ns=3;s=services.WaitForEvent.state_machine.CommandOp').set_value( + CommandCodes.stop) + time.sleep(2) + + print('--- Reset service ---') + opcua_client.get_node('ns=3;s=services.WaitForEvent.state_machine.CommandOp').set_value( + CommandCodes.reset) + time.sleep(1) + + print('--- Set service to Offline mode ---') + opcua_client.get_node('ns=3;s=services.WaitForEvent.op_src_mode.StateOffOp').set_value(True) + time.sleep(1) + + print('--- Demo complete ---') + opcua_client.disconnect() + module.get_opcua_server().stop() diff --git a/examples/virtual_pea_with_pause_hold_restart.py b/examples/virtual_pea_with_pause_hold_restart.py new file mode 100644 index 0000000..a6fa55f --- /dev/null +++ b/examples/virtual_pea_with_pause_hold_restart.py @@ -0,0 +1,175 @@ +import time +import logging + +from opcua import Client + +from mtppy.opcua_server_pea import OPCUAServerPEA +from mtppy.service import Service +from mtppy.procedure import Procedure +from mtppy.parameter_elements import * +from mtppy.indicator_elements import * +from mtppy.active_elements import * + +from mtppy.command_codes import CommandCodes +from mtppy.state_codes import StateCodes + +CommandCodes = CommandCodes() +StateCodes = StateCodes() + +logging.basicConfig( + format='%(asctime)s.%(msecs)03d; [%(levelname)s]; ' + '%(module)s.%(funcName)s: %(message)s', + datefmt='%H:%M:%S', level=logging.INFO) +logging.getLogger("opcua").setLevel(logging.ERROR) +logging.getLogger("mtppy.service").setLevel(logging.DEBUG) + +_log = logging.getLogger(__name__) + + +class Counter(Service): + def __init__(self, tag_name, tag_description): + super().__init__(tag_name, tag_description) + + # Procedure definition + proc_1 = Procedure(1, 'cont', is_self_completing=True, is_default=True) + + # Adding procedure report value + proc_1.add_report_value( + DIntView('count', v_scl_min=0, v_scl_max=100, v_unit=23), + ) + + # Allocating procedure to the service + self.add_procedure(proc_1) + + # Enable pause and hold functionality + self.enable_pause_loop() + self.enable_hold_loop() + + # Enable reset functionality + # self.enable_restart() + + # add count variable + self.count = 0 + + def starting(self): + _log.info('- Starting -') + self.count = 0 + self.get_current_procedure().report_values['count'].set_v(self.count) + # time.sleep(1) + + def execute(self): + _log.info('- Execute -') + start_time = int(time.time() % 100) + # state_stop_event = self.get_state_stop_event() + # _log.info(f"State stop event: {state_stop_event.is_set()} for start at {start_time}") + + while self.is_state("execute") and self.count < 100: + + # Execute counting + self.count += 1 + self.get_current_procedure().report_values['count'].set_v(self.count) + _log.info(f'Count value: {self.count:3d} start at {start_time}') + + time.sleep(0.1) + + def completing(self): + _log.info('- Completing -') + + _log.info(f'Final count value: {self.count}') + + # starting, execute, completing methods have to be defined for each service + + # if enabled at least one of the pausing/ holding functions should be implemented + def pausing(self): + _log.info(f'Count value at pausing: {self.count}') + + def paused(self): + _log.info(f"Count value at paused: {self.count}") + + def resuming(self): + return super().resuming() + + def holding(self): + _log.info(f"Count value at holding: {self.count}") + + def hold(self): + _log.info(f'Count value at hold: {self.count}') + + def unholding(self): + return super().unholding() + + def restarting(self): + _log.info('- Restarting -') + + _log.info("Resetting count value") + # other state methods (see service documentation) can be overridden as needed, + # by default they only log state entries + + +if __name__ == '__main__': + + module = OPCUAServerPEA(endpoint="opc.tcp://127.0.0.1:4840/") + + # Service definition + service_1 = Counter('counter', 'Counts from 0 to 100') + module.add_service(service_1) + + # Start server + print('--- Start OPC UA server ---') + module.run_opcua_server() + + ############################################################################################### + # Test + opcua_client = Client(module.endpoint) + opcua_client.connect() + time.sleep(1) + + print('--- Set service to Operator mode ---') + opcua_client.get_node('ns=3;s=services.counter.op_src_mode.StateOpOp').set_value(True) + time.sleep(1) + + print('--- Start service ---') + opcua_client.get_node('ns=3;s=services.counter.state_machine.CommandOp').set_value( + CommandCodes.start) + time.sleep(4) + + print('--- Restart service ---') + opcua_client.get_node('ns=3;s=services.counter.state_machine.CommandOp').set_value( + CommandCodes.restart) + time.sleep(4) + + print('--- Pause service ---') + opcua_client.get_node('ns=3;s=services.counter.state_machine.CommandOp').set_value( + CommandCodes.pause) + time.sleep(2) + + print('--- Resume service ---') + opcua_client.get_node('ns=3;s=services.counter.state_machine.CommandOp').set_value( + CommandCodes.resume) + time.sleep(4) + + print('--- Hold service ---') + opcua_client.get_node('ns=3;s=services.counter.state_machine.CommandOp').set_value( + CommandCodes.hold) + time.sleep(2) + + print('--- Unhold service ---') + opcua_client.get_node('ns=3;s=services.counter.state_machine.CommandOp').set_value( + CommandCodes.unhold) + + print('--- Waiting until service completes ---') + while opcua_client.get_node('ns=3;s=services.counter.state_machine.StateCur').get_value() != StateCodes.completed: + time.sleep(1) + + print('--- Reset service ---') + opcua_client.get_node('ns=3;s=services.counter.state_machine.CommandOp').set_value( + CommandCodes.reset) + time.sleep(1) + + print('--- Set service to Offline mode ---') + opcua_client.get_node('ns=3;s=services.counter.op_src_mode.StateOffOp').set_value(True) + time.sleep(1) + + print('--- Demo complete ---') + opcua_client.disconnect() + module.get_opcua_server().stop() diff --git a/examples/virtual_pea_with_recipe.py b/examples/virtual_pea_with_recipe.py index 903fcd0..950e9e0 100644 --- a/examples/virtual_pea_with_recipe.py +++ b/examples/virtual_pea_with_recipe.py @@ -1,13 +1,31 @@ +import time +import random +from datetime import datetime +import logging + +from opcua import Client + from mtppy.opcua_server_pea import OPCUAServerPEA from mtppy.mtp_generator import MTPGenerator from mtppy.service import Service from mtppy.procedure import Procedure -from mtppy.operation_elements import * +from mtppy.parameter_elements import * from mtppy.indicator_elements import * -import time -import random -from datetime import datetime +from mtppy.command_codes import CommandCodes +from mtppy.state_codes import StateCodes + +CommandCodes = CommandCodes() +StateCodes = StateCodes() + +logging.basicConfig( + format='%(asctime)s.%(msecs)03d; [%(levelname)s];' + '%(module)s.%(funcName)s: %(message)s', + datefmt='%H:%M:%S', level=logging.INFO) +logging.getLogger("opcua").setLevel(logging.ERROR) +logging.getLogger("mtppy.service").setLevel(logging.DEBUG) + +_log = logging.getLogger(__name__) class ServiceDummy(Service): @@ -18,24 +36,28 @@ def __init__(self, tag_name, tag_description): def add_service_parameters(self): serv_parameters = [AnaServParam('serv_param_ana', v_min=0, v_max=50, v_scl_min=0, v_scl_max=10, v_unit=23), - DIntServParam('serv_param_dint', v_min=-10, v_max=10, v_scl_min=0, v_scl_max=-10, v_unit=23), - BinServParam('serv_param_bin', v_state_0='state_0', v_state_1='state_1'), + DIntServParam('serv_param_dint', v_min=-10, v_max=10, + v_scl_min=0, v_scl_max=-10, v_unit=23), + BinServParam('serv_param_bin', v_state_0='state_0', + v_state_1='state_1'), StringServParam('serv_param_str') ] [self.add_configuration_parameter(serv_param) for serv_param in serv_parameters] def add_procedures(self): # Procedure 1 - proc_1 = Procedure(0, 'proc_1', is_self_completing=False, is_default=False) + proc_1 = Procedure(1, 'proc_1', is_self_completing=False, is_default=False) # Procedure 2 - proc_2 = Procedure(1, 'proc_2', is_self_completing=True, is_default=True) + proc_2 = Procedure(2, 'proc_2', is_self_completing=True, is_default=True) # Procedure 3 - proc_3 = Procedure(2, 'proc_3', is_self_completing=True, is_default=False) + proc_3 = Procedure(3, 'proc_3', is_self_completing=True, is_default=False) proc_parameters = [AnaServParam('proc_param_ana', v_min=0, v_max=50, v_scl_min=0, v_scl_max=10, v_unit=23), - DIntServParam('proc_param_dint', v_min=-10, v_max=10, v_scl_min=0, v_scl_max=-10, v_unit=23), - BinServParam('proc_param_bin', v_state_0='state_0', v_state_1='state_1'), + DIntServParam('proc_param_dint', v_min=-10, v_max=10, + v_scl_min=0, v_scl_max=-10, v_unit=23), + BinServParam('proc_param_bin', v_state_0='state_0', + v_state_1='state_1'), StringServParam('proc_param_str'), ] [proc_3.add_procedure_parameter(proc_param) for proc_param in proc_parameters] @@ -51,86 +73,40 @@ def add_procedures(self): self.add_procedure(proc_2) self.add_procedure(proc_3) - def idle(self): - print('- Idle -') - cycle = 0 - while self.is_state('idle'): - print('Idle cycle %i' % cycle) - cycle += 1 - time.sleep(1) - def starting(self): - print('- Starting -') - self.state_change() + _log.info('- Starting -') def execute(self): - print('- Execute -') + _log.info('- Execute -') cycle = 0 while self.is_state('execute'): - print('Execute cycle %i' % cycle) - print(f'ProcedureCur is {self.procedure_control.get_procedure_cur()}') - print('ServParameter %s has value %r' - % (self.configuration_parameters['serv_param_ana'].tag_name, - self.configuration_parameters['serv_param_ana'].get_v_out())) - print('ServParameter %s has value %r' - % (self.configuration_parameters['serv_param_dint'].tag_name, - self.configuration_parameters['serv_param_dint'].get_v_out())) - print('ServParameter %s has value %r' - % (self.configuration_parameters['serv_param_bin'].tag_name, - self.configuration_parameters['serv_param_bin'].get_v_out())) - print('ServParameter %s has value %r' - % (self.configuration_parameters['serv_param_str'].tag_name, - self.configuration_parameters['serv_param_str'].get_v_out())) - - if self.procedure_control.get_procedure_cur() == 2: - self.procedures[2].report_values['proc_rv_ana'].set_v(random.random()) - self.procedures[2].report_values['proc_rv_bin'].set_v( - not self.procedures[2].report_values['proc_rv_bin'].attributes['V'].value) - self.procedures[2].report_values['proc_rv_dint'].set_v(random.randint(-100, 100)) - self.procedures[2].report_values['proc_rv_str'].set_v(str(random.random())) + _log.info('Execute cycle %i' % cycle) + _log.info(f'ProcedureCur is {self.procedure_control.get_procedure_cur()}') + _log.info('ServParameter %s has value %r' + % (self.configuration_parameters['serv_param_ana'].tag_name, + self.configuration_parameters['serv_param_ana'].get_v_out())) + _log.info('ServParameter %s has value %r' + % (self.configuration_parameters['serv_param_dint'].tag_name, + self.configuration_parameters['serv_param_dint'].get_v_out())) + _log.info('ServParameter %s has value %r' + % (self.configuration_parameters['serv_param_bin'].tag_name, + self.configuration_parameters['serv_param_bin'].get_v_out())) + _log.info('ServParameter %s has value %r' + % (self.configuration_parameters['serv_param_str'].tag_name, + self.configuration_parameters['serv_param_str'].get_v_out())) + + if self.procedure_control.get_procedure_cur() == 3: + self.procedures[3].report_values['proc_rv_ana'].set_v(random.random()) + self.procedures[3].report_values['proc_rv_bin'].set_v( + not self.procedures[3].report_values['proc_rv_bin'].attributes['V'].value) + self.procedures[3].report_values['proc_rv_dint'].set_v(random.randint(-100, 100)) + self.procedures[3].report_values['proc_rv_str'].set_v(str(random.random())) cycle += 1 time.sleep(1) def completing(self): - self.state_change() - - def completed(self): - pass - - def pausing(self): - pass - - def paused(self): - pass - - def resuming(self): - pass - - def holding(self): - pass - - def held(self): - pass - - def unholding(self): - pass - - def stopping(self): - pass - - def stopped(self): - pass - - def aborting(self): - pass - - def aborted(self): - pass - - def resetting(self): - print('- Resetting -') - self.state_change() + _log.info('- Completing -') if __name__ == '__main__': @@ -140,7 +116,8 @@ def resetting(self): 'WriterVersion': '1.0.0', 'WriterRelease': '', 'LastWritingDateTime': str(datetime.now()), 'WriterProjectTitle': 'tu/plt/mtp', 'WriterProjectID': ''} export_manifest_path = '../manifest_files/example_recipe_manifest.aml' - mtp_generator = MTPGenerator(writer_info_dict, export_manifest_path) + manifest_template_path = '../manifest_files/manifest_template.xml' + mtp_generator = MTPGenerator(writer_info_dict, export_manifest_path, manifest_template_path) module = OPCUAServerPEA(mtp_generator) @@ -153,52 +130,65 @@ def resetting(self): module.run_opcua_server() # Test - opcua_server = module.get_opcua_server() - opcua_ns = module.get_opcua_ns() + opcua_client = Client(module.endpoint) + opcua_client.connect() time.sleep(1) print('--- Set parameters of service dummy to Operator mode ---') - opcua_server.get_node( + opcua_client.get_node( 'ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateOpOp').set_value(True) - opcua_server.get_node( + opcua_client.get_node( 'ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateOpOp').set_value(True) - opcua_server.get_node( + opcua_client.get_node( 'ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateOpOp').set_value(True) - opcua_server.get_node( + opcua_client.get_node( 'ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateOpOp').set_value(True) time.sleep(1) print('--- Set parameters VOp of service dummy to different values ---') - opcua_server.get_node('ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VOp').set_value(10.54) - opcua_server.get_node('ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VOp').set_value(-5.11) - opcua_server.get_node('ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VOp').set_value(True) - opcua_server.get_node('ns=3;s=services.dummy.configuration_parameters.serv_param_str.VOp').set_value('hello there') + opcua_client.get_node( + 'ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VOp').set_value(10.54) + opcua_client.get_node( + 'ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VOp').set_value(-5.11) + opcua_client.get_node( + 'ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VOp').set_value(True) + opcua_client.get_node( + 'ns=3;s=services.dummy.configuration_parameters.serv_param_str.VOp').set_value('hello there') time.sleep(1) print('--- Change procedure to 2 ---') - opcua_server.get_node('ns=3;s=services.dummy.procedure_control.ProcedureOp').set_value(2) + opcua_client.get_node('ns=3;s=services.dummy.procedure_control.ProcedureOp').set_value(2) time.sleep(1) print('--- Set service dummy to Operator mode ---') - opcua_server.get_node('ns=3;s=services.dummy.op_src_mode.StateOpOp').set_value(True) + opcua_client.get_node('ns=3;s=services.dummy.op_src_mode.StateOpOp').set_value(True) time.sleep(2) print('--- Start service dummy ---') - opcua_server.get_node('ns=3;s=services.dummy.state_machine.CommandOp').set_value(4) - time.sleep(500) + opcua_client.get_node('ns=3;s=services.dummy.state_machine.CommandOp').set_value( + CommandCodes.start) + time.sleep(10) print('--- Try to unhold service dummy ---') - opcua_server.get_node('ns=3;s=services.dummy.state_machine.CommandOp').set_value(32) + opcua_client.get_node('ns=3;s=services.dummy.state_machine.CommandOp').set_value( + CommandCodes.unhold) time.sleep(2) print('--- Complete service dummy ---') - opcua_server.get_node('ns=3;s=services.dummy.state_machine.CommandOp').set_value(1024) + opcua_client.get_node('ns=3;s=services.dummy.state_machine.CommandOp').set_value( + CommandCodes.complete) time.sleep(1) print('--- Reset service dummy ---') - opcua_server.get_node('ns=3;s=services.dummy.state_machine.CommandOp').set_value(2) + opcua_client.get_node('ns=3;s=services.dummy.state_machine.CommandOp').set_value( + CommandCodes.reset) + time.sleep(1) print('--- Set service dummy to Offline mode ---') - opcua_server.get_node('ns=3;s=services.dummy.op_src_mode.StateOffOp').set_value(True) + opcua_client.get_node('ns=3;s=services.dummy.op_src_mode.StateOffOp').set_value(True) time.sleep(1) + + print('--- Demo complete ---') + opcua_client.disconnect() + module.get_opcua_server().stop() diff --git a/manifest_files/example_minimal_manifest.aml b/manifest_files/example_minimal_manifest.aml index 0d20e80..dfd00d1 100644 --- a/manifest_files/example_minimal_manifest.aml +++ b/manifest_files/example_minimal_manifest.aml @@ -17,7 +17,7 @@ www.tud.de 1.0.0 - 2022-08-19 18:45:58.883245 + 2025-10-26 16:33:34.065599 tu/plt/mtp @@ -2355,153 +2355,159 @@ - - + + 1.0.0 - - - + + + rand_num_gen This services generates random number - - 0 - DG3F-agAc-7UQ4-H7pG-vyfN - 255 - OyQ1-lAaU-M0N3-k9qp-6oL5 + qy8r-oydw-OK6h-X7jG-vhew + + + 0 + YriX-2HPW-bN1f-7xRw-EP7g 0 - hi6Y-Y2RM-J1kb-Gm8V-4EeO + hgqX-vAP6-5W6k-mQ8C-z7i2 0 - j1H7-HWQx-1wor-Qke8-dV2h + QCIp-lBwp-HsCY-9hr2-vr0T 0 - GE25-ykm6-vNC8-xqgp-QvAO + Hv5t-N2zf-4vqp-4Vlz-gbel False - RxI2-hAUI-9RGh-xeGK-9gCT + Mujr-27zs-OYb9-mt7S-H9Rh False - oIUR-CsH6-w12J-OnVH-jVmR + mJWV-MN8U-sA89-JvE4-M7nX False - cBgH-NOSL-qHOA-QMsN-rzli + rjKz-es36-aqOS-43xg-lmfc False - b7oO-vCFL-mCpg-UElL-itW8 + ANtP-KZsn-3PB6-rPkJ-TQyK False - jJIV-0hIS-K4iW-KvoA-5Wiz + oqwi-7QUg-PL2p-PMu2-pcxf False - SbGF-n3Th-YXKO-0JDg-ewMu + BpaM-jW17-mE6K-4fWQ-ExFt False - vP1s-tWms-TY5N-pM72-CJy0 + 4tuq-HCTN-Bj5Y-Tk6x-JoE4 False - 5eZ8-KhM6-YR29-LAfx-wQDE + cODk-U0AM-AqRn-YfwA-o89k False - kP6j-nmTQ-QtLr-rMhB-bALl + P6zU-q3u4-Ldy1-Yg6X-TPQC True - UsvK-B1v9-EJ3s-07dA-srjl + Epz3-Byzi-Z5dR-bKsU-63ZP False - enKT-YXF0-fLY7-wS6K-R6wJ + cW5d-N9ZR-k9eq-Ipd8-K5fX False - pDjQ-qsyb-rIOG-WZmB-Dnzu + oVvF-XiBc-6ry8-BYmK-nNMG False - 3EmL-iJY8-hzmG-Ulki-W1zy + Hrxp-eHxg-0Q2A-MABU-oBXm False - pj2L-1GAd-fRv4-MgyU-HuM5 + 6ZPL-loB6-N0VT-IpTC-5K8w False - 0xQE-k59L-JcB3-9hXn-6HmS + Q4gO-ZYpf-YJ67-JKq5-qGlH False - 24W9-RUmj-8mIF-eM1d-4NS0 + xHQj-qA4z-FcZT-cUYg-jhMp False - BZ4l-ELaW-pGZ7-fLGY-2Npj + GTfr-PujU-U91R-fTM7-SH4V - - 0 - eniN-JyA5-i5aP-yrhw-zLsM + + ZME4-BY5b-xYo0-fTsD-w35X - - 0 - K2zx-HrXP-Ktqb-62oV-VsFh + + ZME4-BY5b-xYo0-fTsD-w35X - - 0 - dyZu-g43v-i39z-Bocl-FUBR + + 1 + yUZn-Jjce-u6r2-l9GE-z12a - - 16 - FHKf-ji0g-Mw2k-AqkE-NbPg + + 1 + 23xa-WzSy-Dizq-vR1h-qY76 - - 0 - NGcL-zgMG-81nM-FIMN-nUoZ + + 1 + eazg-oETc-USsn-eCTL-bE6h - + 0 - ByLT-EL5F-8gIh-07gs-jTSG + fCOg-C1xy-lH7C-fHRe-JLMa - + + 1 + 9hbt-kr3E-ENDe-vt8Y-z10f + + 0 - DN8v-gACd-5OTJ-5umT-8GOx + gs3b-Yi48-kgLY-CJ6y-xfIN - + 0 - fWau-QYJZ-htU2-7VZE-3scw + 3O9I-3saC-hmUJ-btGd-NCFw - + 0 - PjwS-SkUQ-uJ8n-pPsI-Nij3 + ZTXt-gJYc-5GdE-i187-u4qW - + + 16 + wohG-ohIS-VpgZ-Enxd-VndK + + 0 - Ss87-SQjo-pHPG-GfnQ-Swyd + HJu8-aHP3-g6jO-Wj9E-MDjP - riqc-7XYh-Xufb-dnF1-Z8Gq + ZME4-BY5b-xYo0-fTsD-w35X - + cont @@ -2510,286 +2516,298 @@ 255 - JSFi-CrHo-qbc7-aoKX-Y56d + Jynk-Elvp-ZrKd-qz7n-0PO9 + + + nieu-k9TJ-c31P-Pp0D-9d1F + + + nieu-k9TJ-c31P-Pp0D-9d1F + + + nieu-k9TJ-c31P-Pp0D-9d1F + + + nieu-k9TJ-c31P-Pp0D-9d1F - wXfS-FX8T-7QC0-5wkJ-Owzp + nieu-k9TJ-c31P-Pp0D-9d1F - + lower_bound - - 0 - xi4p-uhWG-d5Om-U8VP-FVqE - 255 - HSoC-L2Pn-b4dv-kjUP-esuH + 7eHI-ibT2-8l1O-Iz7O-zCcb + + + 0 + CAO3-eW3A-lBL4-Ftzm-hqDI 0 - 2RgF-SBfj-oSU4-u3rg-osx5 + o3D2-7yR8-Lziu-XMYQ-EKh3 0 - 26L3-0ahM-gf38-x0rT-4BHM + NzSZ-kfeC-ls6Y-ZplC-PViZ 0 - Ot1l-CTZW-veLZ-4QrB-I06U + hxAF-lHEW-INKq-qxZo-TckJ 0 - jhbM-xwHT-tbdB-rv3D-iEPr + pJEH-gMGF-G9kb-AGxu-fWUY 0 - 8iN5-8FVd-dFBm-JIlx-WYVf + mIKF-LJcn-L4k9-0Qts-AOBr 0 - CXmH-Xvqp-xDqM-eZg3-m8ny + 9J3w-Z1jb-leL2-BbqP-uXlh 23 - Hlsd-0t2P-POzh-WpyR-tPAL + yIkZ-HxoG-Gv37-GZlm-IekO 0 - jatA-O3Zw-fzRl-CUkO-C6cQ + Y2wN-wnCh-Hqgh-XnAS-to8u 100 - 1pKS-q4uG-NkSg-j4or-qRb0 + AsD8-ay4P-QaDJ-uB0A-ULR1 0 - qutT-s4em-u0Iy-YW23-fUTI + F6Is-vU1R-RY8K-XrUJ-AjfM 100 - 4MOJ-iTga-mACt-fbXZ-3TDa + 1KXB-dIu0-BQ1r-rgT4-LFvg False - dlew-BCsr-QspF-jzWZ-iG4a + bWK6-RHLJ-zX5e-76F0-ZxXp False - 8FGT-I8wG-1ozd-P4Tx-C0uS + hJ4x-Yij7-69lW-0zKJ-mpPF False - DuoH-noGz-Wd2P-fjQT-INOM + uJks-4mys-tsOg-TFGZ-ZXOp False - RMCq-PYOG-3CMv-4NiK-uN0E + jBPD-xNS3-0hBb-2Zt5-8swZ False - IgWv-XBzU-OjdE-2BLV-JOR1 + qUMX-WxjC-91yh-lEKR-Ix25 False - QmjP-tqa1-ghFo-Nyxd-Hg0c + Cpin-2hEd-NkrM-5y4m-bvxa False - TnhM-poIv-5r9D-Eubl-DAiR + wck2-tzRV-aN3M-ZDyv-ofJH False - 89xK-nfYO-mXj0-PpkA-0U4B + x5rB-OXsj-GNkp-vxuU-uIUj False - 5eLI-Ybt4-CgIE-FNpa-aIBe + Jitq-kOce-6PN2-scAM-tP3d False - u0P9-UaBw-oEnU-Oa5t-B8Y9 + sRpC-pXbA-EzDn-rCGB-1Ovb True - kWUI-KzBV-HrsK-17i0-54h8 + GX7u-J20c-E8RU-UVvQ-1Caz False - 7RUz-IrsO-GeNo-jGtO-1zIk + yU5X-RBfa-zEAX-Aza4-PjyL False - VOA5-In5c-G6C2-64HS-FNAd + KAQP-HCxu-UhLi-pRWO-V0bE False - vEKi-mjQX-9Jhr-Goq4-GJBy + MIYL-h0U6-5ACE-DydW-oR8v False - QRfm-IfhY-IpiZ-9zby-9jhe + EF0N-wSBq-P8bG-f6mK-ktP9 False - u9hB-VY5T-Xv0x-Vm3Q-OngJ + 7Lt6-wbn2-bkNJ-iS09-cwyC False - MZ6F-dqGB-TaIo-fk17-dtgr + xtEQ-kE0W-j3V6-CYGi-YSa9 False - YJ9r-G4mq-1neH-NCmg-UzZH + vxza-HRNL-4RaE-02qW-7ORd - BSNu-qUTn-Eawh-CRKr-LvcS + foXx-WB2I-4coD-vAqY-LPeZ - + upper_bound - - 0 - EPBV-AO2s-MJs6-bDQc-mbaj - 255 - P7WM-AG1i-En6Y-JvqU-CG1m + AdaW-swzh-5uJB-L4O8-7Hka + + + 0 + 8UdT-Z8O9-Uned-8zTi-fMaV 0 - cdP9-LJ4U-pZlC-Lwz8-aZun + Gj5c-RPng-eNr0-nIDw-O2Jv 0 - Z0Ba-w7MU-igBp-61Vw-4sGz + 5mtg-imkv-y3xj-SuZ8-YAq2 0 - s5ft-YFKb-suVH-S1az-ASk8 + 5UJ4-pyXc-5eqo-obi4-lNfv 0 - wkgA-8RqZ-i0Bb-osf8-NhpF + vpKe-aClI-0lMh-ISLG-4jTo 0 - y3vH-CdY4-Ciup-MdLw-e6xy + mXOu-EfQm-hEpe-DwjO-Yz5Z 0 - sceA-cxme-clZA-yxmZ-80k7 + Kr15-k60T-SfKt-7jY2-7gIQ 23 - Hf82-nd5f-f2yA-JVhN-juJM + OcUy-Pk5e-WsLV-VN9h-LOgS 0 - p42H-0z8j-3bZv-bzPA-ghtp + lK2U-UIxW-stCN-t5C7-J753 100 - XwWu-pXur-gLF6-TiEz-qKmJ + rRpT-Nrmo-aqxR-Z94z-xvNo 0 - 68my-j0IX-oBx0-QLCu-p0NE + DO8w-2ARo-IVex-I7od-ifzS 100 - q5Bo-qeQT-PvmR-sxJ4-D0he + A4Cx-eU3n-8h7H-mrQK-gATa False - LIJS-xkiM-ic6Q-DczM-GZ9p + BRoY-8L4K-zb0Z-x2Yn-7bDM False - 4nhv-wmab-2szt-H0XA-GNHu + mMXQ-yI7O-BROd-MDlY-JfcT False - prvE-4Vne-jJc8-TawP-qVS4 + 8cj3-mUWY-NzhQ-29XR-o08P False - vbjt-2eom-aFtO-JYm6-XAeR + vSin-leGf-VOwS-DLHc-khfd False - ha5M-saCE-KNCD-Sm8B-ryOD + Me1j-eo5S-sPbF-6irh-ziFc False - 1ntL-4R25-lSVz-S3RB-MKRb + XqeE-JsTX-JYwE-CQhe-p9IU False - e8q4-BmGN-8QR1-yeFi-Lb4N + 29cZ-IbQC-30SU-UHyq-nKfo False - lKPL-uv01-XaKu-F9Hr-MVWZ + rTPe-TpGV-ZrYi-k8LE-Gw0M False - rZyA-ClFi-m5Sr-1WCn-jsBR + 8HBE-xjUy-Vtx3-nPse-q7gZ False - pxlg-DcIg-1YMQ-LRXJ-SqmN + 5xU2-wHxh-pfCs-EZBV-Kiud True - Kihs-MxSA-qSVs-26yP-61RG + zwEx-pMnG-PdHu-7J9y-jCn8 False - lZi7-i9n3-B4an-64DA-evLR + jihb-Jf1H-wJpa-D8TR-YylS False - 4lUS-wlpW-bPx3-LU4P-6t7S + vbRP-wqpy-P57q-hHOx-bo96 False - LuIh-lfsO-Zomt-H8aJ-JpHv + DUmi-5WFH-CV7P-FWwq-XJvP False - fLrS-BE7R-Ozuj-IhGg-nqkj + vgTL-4OeE-qp7J-a8PC-z3bx False - PRwW-B946-QV08-7Nbs-Nnhu + aj4V-5ES7-YwRq-2cHS-MwXF False - XUqY-e7ag-Qieo-LyO4-5Ufy + TA7K-osOc-vNmP-lZaM-kyRN False - i6aM-yOEg-CDzQ-pc2z-rai4 + TEtg-Rf10-OysK-fehX-IyOq - CXUf-VdDC-pa83-2V4m-ya8r + ofXQ-0oA6-h3iB-pi8y-09yB - + generated_value @@ -2798,34 +2816,34 @@ 0 - LJNe-XueK-fIcj-6TDF-bdVW + SoTx-79JG-Aesw-eMFm-wJDP 255 - a2Op-Wwdz-W49V-UkQV-Xw5I + 2VUv-L0X5-bpzk-pkga-hBu2 0.0 - g7Bz-D03Q-ytB2-3nH2-p4XM + TBno-weZa-y6HR-IEGC-lW2p 23 - l8BY-sH0O-fKlz-F3mX-7G5s + pydD-bavz-Hbp2-62H4-rSo8 0.0 - 1hiN-hUAq-BmRw-gCnb-16HJ + 3s1N-7Z8Q-zsdo-NSyK-GAlp 100.0 - TB31-TP6q-Xny3-dL6C-YHbG + 0wjb-dJjh-SdFZ-ZhFC-ULoi - iSh9-49dt-spIx-SquA-4ia9 + TFbt-FvCk-Jmv4-kXFv-r19v - + pid_ctrl @@ -2834,189 +2852,189 @@ 0 - LSgI-rD5b-tlkC-3R48-xDBo + 7PiR-fRFK-40wP-F9Up-bLl6 255 - Q27i-aMPk-3nv6-cST9-zvwm + oEAg-aqn1-kqKi-F1ay-Cp9G 0.0 - IVyM-Ub4I-THin-vVc3-6Pnt + wt4V-ldZ6-GgNT-tdoQ-YEsy 0.0 - jThP-64PC-B17n-wA8C-yo8J + GHsp-bR3A-MtYO-rUdC-nAax 100.0 - sN9I-joRm-OJCF-Gpgv-McVs + NYhl-Yh67-74jN-L5eE-FAtj 0 - YTCX-6wtM-PIZU-2N4W-K5HN + hHjy-6vON-Al4p-Nl92-2zcl 0.0 - mROo-lL0p-l4L5-9E0D-NFuU + dH0s-0TIS-uAWl-2oqD-XZYg 0.0 - qhFv-9NKZ-7oQk-kFo6-fxXe + KW8b-1vNP-eBYl-TZwX-M2hq 0.0 - zU3Q-CIAv-vdjM-CF9P-QgUz + 4E6y-fqLW-3Lzq-f3ue-ISha 100.0 - BEf2-5BU6-mUrq-UoPa-AVUh + VJc5-of5p-JfRZ-0yvA-LCzS 0 - y3Ox-hoAG-L3RG-pwNq-jp1f + iYjo-G5El-4Hdu-LOxl-pn83 0.0 - ONYH-GWqm-BEUK-Uv5z-fwu9 + UDkE-ZT7j-L6XC-c4Lr-8EIr 100.0 - JbYp-sdrH-NJdP-EOrG-cF6T + abDs-aMkZ-wli9-zNPm-OeIn 0.0 - 95fn-KqmM-1zdW-OI0U-8zsS + IKjL-j56g-xw7v-jCHU-cwGa 100.0 - KBPo-WNVm-wCjO-dh0l-KDLW + X7pM-M1Xv-xOwr-FJ3k-9TSk 0.0 - 2X9d-fRXH-gtQO-lFkN-XnZf + IEWz-Yiyd-UINT-uO9b-CQTU 0.0 - fv7D-3c6N-DPo2-Qvqf-H6op + MhW5-rH2A-NXZT-dQho-WKUn 0.0 - JS1Q-vlnz-15EW-2W9s-hNpY + mjXP-lWsp-2QoI-w6qD-tZGD 0.0 - g8dt-HGlE-SVD8-MfnS-6jKd + dLgr-hgCL-3WaL-u7St-AV3p 1000.0 - lq8R-IBb4-cKTR-we1M-dIjS + AKx0-gDIt-kgOQ-K4ET-I34C 0.0 - SXOx-ObE1-KmGS-yLa8-fdji + 1Ldm-Ns18-qNzW-yYz4-Obrh 0.0 - bBpU-Lyrq-Qr8k-ShiG-yt6n + Dd5R-uME2-tFVP-OV12-jMZ7 100.0 - idWh-Vsjb-57RJ-dleu-I5aE + DLUW-efuF-yFkb-rRqd-dASQ 100.0 - eFRL-tY8q-YpGr-dGYX-8X5M + rsD4-PCMb-t5Xc-RnD5-khxi 10.0 - UvNP-HTLJ-xqi2-er09-GhOT + P0jU-3AT0-U0Zi-T1fg-AruL 1.0 - oPwx-l9cF-wcj9-7HLo-SLPX + KNbp-NVFi-Jk68-GfKR-6lM9 False - en1j-Nopq-Um0D-7AQz-jWlA + 2Ae8-w3MX-DqsJ-VA8Z-wa8X False - J2Kd-s6lq-pIox-SHrk-9SoX + SojF-t8gV-fVC3-WkjR-yglH False - 2nRH-n26d-Or6S-K65d-Br6i + lMr0-dHkI-LMki-rtn7-jMP6 False - mNiB-8Irn-UheN-Ef7c-7SNK + izuE-APkw-8GoW-Gwi9-SaoZ False - jtN8-XbWl-T9fn-0Ngx-EzY8 + bxW8-ROwl-GqCa-HvUX-cJ8O False - N5ib-42N5-mhYS-w7u6-rA96 + DBEI-nFCh-wxiP-EaIO-Ne0D False - 7kbs-Dg14-xSmV-j54N-2mCW + 96Uq-Q2cW-IUGu-WmuO-Tc0a False - LijD-4Weo-Rx9H-pM8d-x69e + 5VGE-0dLG-Mvt9-dLti-FshJ False - aKRJ-meL8-jK8O-6DdT-df2I + 1Sbc-ZR7n-QNpm-49qy-34rP True - i57N-5m8r-Plk1-6FSv-NEK7 + qMKz-3RYI-cMfa-nbgz-9sPf False - 9zo2-pgSV-mkow-FfBT-7Zcm + 8Cyc-a30j-Pp79-tPdh-fIuk - + False - Cx3a-IRgF-erQY-XY7i-dYFD + vblO-wPFv-eIdF-mkBK-MDL4 False - EhTM-iwMS-n5pw-clO0-n4rE + uzbn-kheu-h6ZX-skMt-U0Ki False - Eo5k-flTO-xX6J-T6Bb-8XUe + WJgl-opt4-Y3FJ-gq3E-nPQm - + False - AXht-H9sC-n9iC-o58Z-5oOG + FeHc-f91D-xBd3-jzcw-I6Bg False - lUSW-2xyo-2Ruv-PseW-kbuC + blu8-I91E-Ei3c-gsl9-o07R - + False - XJOQ-qSVm-qF7H-jIpg-D7H1 + NjGW-OH8G-Ze9Q-saWJ-Xi9q - P8Un-K0Mf-eCTJ-ovOa-ENAl + Lz98-NVPL-TpJj-1zKS-KLAt - - + + opc.tcp://127.0.0.1:4840/ - + ns=3;s=services.rand_num_gen.tag_name @@ -3027,7 +3045,7 @@ 1 - + ns=3;s=services.rand_num_gen.tag_description @@ -3038,9 +3056,9 @@ 1 - + - ns=3;s=services.rand_num_gen.OSLevel + ns=3;s=services.rand_num_gen.WQC 3 @@ -3049,9 +3067,9 @@ 1 - + - ns=3;s=services.rand_num_gen.WQC + ns=3;s=services.rand_num_gen.OSLevel 3 @@ -3060,7 +3078,7 @@ 1 - + ns=3;s=services.rand_num_gen.PosTextID @@ -3071,7 +3089,7 @@ 1 - + ns=3;s=services.rand_num_gen.InteractQuestionID @@ -3082,7 +3100,7 @@ 1 - + ns=3;s=services.rand_num_gen.InteractAnswerID @@ -3093,9 +3111,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.tag_name + ns=3;s=services.rand_num_gen.op_src_mode.StateChannel 3 @@ -3104,9 +3122,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.tag_description + ns=3;s=services.rand_num_gen.op_src_mode.StateOffAut 3 @@ -3115,9 +3133,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.WQC + ns=3;s=services.rand_num_gen.op_src_mode.StateOpAut 3 @@ -3126,9 +3144,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.IsSelfCompleting + ns=3;s=services.rand_num_gen.op_src_mode.StateAutAut 3 @@ -3137,9 +3155,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.ProcedureId + ns=3;s=services.rand_num_gen.op_src_mode.StateOffOp 3 @@ -3148,9 +3166,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.IsDefault + ns=3;s=services.rand_num_gen.op_src_mode.StateOpOp 3 @@ -3159,9 +3177,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.tag_name + ns=3;s=services.rand_num_gen.op_src_mode.StateAutOp 3 @@ -3170,9 +3188,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.tag_description + ns=3;s=services.rand_num_gen.op_src_mode.StateOpAct 3 @@ -3181,9 +3199,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.OSLevel + ns=3;s=services.rand_num_gen.op_src_mode.StateAutAct 3 @@ -3192,9 +3210,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.WQC + ns=3;s=services.rand_num_gen.op_src_mode.StateOffAct 3 @@ -3203,9 +3221,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VOp + ns=3;s=services.rand_num_gen.op_src_mode.SrcChannel 3 @@ -3214,9 +3232,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VInt + ns=3;s=services.rand_num_gen.op_src_mode.SrcExtAut 3 @@ -3225,9 +3243,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VExt + ns=3;s=services.rand_num_gen.op_src_mode.SrcIntOp 3 @@ -3236,9 +3254,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VReq + ns=3;s=services.rand_num_gen.op_src_mode.SrcIntAut 3 @@ -3247,9 +3265,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VOut + ns=3;s=services.rand_num_gen.op_src_mode.SrcExtOp 3 @@ -3258,9 +3276,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VFbk + ns=3;s=services.rand_num_gen.op_src_mode.SrcIntAct 3 @@ -3269,9 +3287,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VUnit + ns=3;s=services.rand_num_gen.op_src_mode.SrcExtAct 3 @@ -3280,9 +3298,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VSclMin + ns=3;s=services.rand_num_gen.procedures.cont.tag_name 3 @@ -3291,9 +3309,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VSclMax + ns=3;s=services.rand_num_gen.procedures.cont.tag_description 3 @@ -3302,9 +3320,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VMin + ns=3;s=services.rand_num_gen.procedures.cont.WQC 3 @@ -3313,9 +3331,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VMax + ns=3;s=services.rand_num_gen.procedures.cont.IsSelfCompleting 3 @@ -3324,9 +3342,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.Sync + ns=3;s=services.rand_num_gen.procedures.cont.ProcedureId 3 @@ -3335,9 +3353,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateChannel + ns=3;s=services.rand_num_gen.procedures.cont.IsDefault 3 @@ -3346,9 +3364,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOffAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.tag_name 3 @@ -3357,9 +3375,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOpAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.tag_description 3 @@ -3368,9 +3386,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateAutAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.WQC 3 @@ -3379,9 +3397,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOffOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.OSLevel 3 @@ -3390,9 +3408,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOpOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VOp 3 @@ -3401,9 +3419,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateAutOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VInt 3 @@ -3412,9 +3430,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOpAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VExt 3 @@ -3423,9 +3441,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateAutAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VReq 3 @@ -3434,9 +3452,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOffAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VOut 3 @@ -3445,9 +3463,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcChannel + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VFbk 3 @@ -3456,9 +3474,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcExtAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VUnit 3 @@ -3467,9 +3485,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcIntOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VSclMin 3 @@ -3478,9 +3496,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcIntAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VSclMax 3 @@ -3489,9 +3507,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcExtOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VMin 3 @@ -3500,9 +3518,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcIntAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.VMax 3 @@ -3511,9 +3529,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcExtAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.Sync 3 @@ -3522,9 +3540,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.tag_name + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateChannel 3 @@ -3533,9 +3551,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.tag_description + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOffAut 3 @@ -3544,9 +3562,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.OSLevel + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOpAut 3 @@ -3555,9 +3573,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.WQC + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateAutAut 3 @@ -3566,9 +3584,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOffOp 3 @@ -3577,9 +3595,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VInt + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOpOp 3 @@ -3588,9 +3606,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VExt + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateAutOp 3 @@ -3599,9 +3617,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VReq + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOpAct 3 @@ -3610,9 +3628,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VOut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateAutAct 3 @@ -3621,9 +3639,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VFbk + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.StateOffAct 3 @@ -3632,9 +3650,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VUnit + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcChannel 3 @@ -3643,9 +3661,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VSclMin + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcExtAut 3 @@ -3654,9 +3672,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VSclMax + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcIntOp 3 @@ -3665,9 +3683,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VMin + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcIntAut 3 @@ -3676,9 +3694,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VMax + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcExtOp 3 @@ -3687,9 +3705,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.Sync + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcIntAct 3 @@ -3698,9 +3716,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateChannel + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.lower_bound.op_src_mode.SrcExtAct 3 @@ -3709,9 +3727,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOffAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.tag_name 3 @@ -3720,9 +3738,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOpAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.tag_description 3 @@ -3731,9 +3749,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateAutAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.WQC 3 @@ -3742,9 +3760,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOffOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.OSLevel 3 @@ -3753,9 +3771,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOpOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VOp 3 @@ -3764,9 +3782,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateAutOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VInt 3 @@ -3775,9 +3793,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOpAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VExt 3 @@ -3786,9 +3804,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateAutAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VReq 3 @@ -3797,9 +3815,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOffAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VOut 3 @@ -3808,9 +3826,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcChannel + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VFbk 3 @@ -3819,9 +3837,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcExtAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VUnit 3 @@ -3830,9 +3848,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcIntOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VSclMin 3 @@ -3841,9 +3859,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcIntAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VSclMax 3 @@ -3852,9 +3870,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcExtOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VMin 3 @@ -3863,9 +3881,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcIntAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.VMax 3 @@ -3874,9 +3892,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcExtAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.Sync 3 @@ -3885,9 +3903,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.tag_name + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateChannel 3 @@ -3896,9 +3914,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.tag_description + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOffAut 3 @@ -3907,9 +3925,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.OSLevel + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOpAut 3 @@ -3918,9 +3936,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.WQC + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateAutAut 3 @@ -3929,9 +3947,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.V + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOffOp 3 @@ -3940,9 +3958,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.VUnit + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOpOp 3 @@ -3951,9 +3969,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.VSclMin + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateAutOp 3 @@ -3962,9 +3980,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.VSclMax + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOpAct 3 @@ -3973,9 +3991,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateChannel + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateAutAct 3 @@ -3984,9 +4002,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateOffAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.StateOffAct 3 @@ -3995,9 +4013,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateOpAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcChannel 3 @@ -4006,9 +4024,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateAutAut + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcExtAut 3 @@ -4017,9 +4035,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateOffOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcIntOp 3 @@ -4028,9 +4046,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateOpOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcIntAut 3 @@ -4039,9 +4057,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateAutOp + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcExtOp 3 @@ -4050,9 +4068,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateOpAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcIntAct 3 @@ -4061,9 +4079,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateAutAct + ns=3;s=services.rand_num_gen.procedures.cont.procedure_parameters.upper_bound.op_src_mode.SrcExtAct 3 @@ -4072,9 +4090,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.StateOffAct + ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.tag_name 3 @@ -4083,9 +4101,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.SrcChannel + ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.tag_description 3 @@ -4094,9 +4112,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.SrcExtAut + ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.OSLevel 3 @@ -4105,9 +4123,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.SrcIntOp + ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.WQC 3 @@ -4116,9 +4134,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.SrcIntAut + ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.V 3 @@ -4127,9 +4145,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.SrcExtOp + ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.VUnit 3 @@ -4138,9 +4156,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.SrcIntAct + ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.VSclMin 3 @@ -4149,9 +4167,9 @@ 1 - + - ns=3;s=services.rand_num_gen.op_src_mode.SrcExtAct + ns=3;s=services.rand_num_gen.procedures.cont.report_values.generated_value.VSclMax 3 @@ -4160,9 +4178,9 @@ 1 - + - ns=3;s=services.rand_num_gen.state_machine.CommandOp + ns=3;s=services.rand_num_gen.procedure_control.ProcedureOp 3 @@ -4171,9 +4189,9 @@ 1 - + - ns=3;s=services.rand_num_gen.state_machine.CommandInt + ns=3;s=services.rand_num_gen.procedure_control.ProcedureInt 3 @@ -4182,9 +4200,9 @@ 1 - + - ns=3;s=services.rand_num_gen.state_machine.CommandExt + ns=3;s=services.rand_num_gen.procedure_control.ProcedureExt 3 @@ -4193,9 +4211,9 @@ 1 - + - ns=3;s=services.rand_num_gen.state_machine.StateCur + ns=3;s=services.rand_num_gen.procedure_control.ProcedureCur 3 @@ -4204,9 +4222,9 @@ 1 - + - ns=3;s=services.rand_num_gen.state_machine.CommandEn + ns=3;s=services.rand_num_gen.procedure_control.ProcedureReq 3 @@ -4215,9 +4233,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedure_control.ProcedureOp + ns=3;s=services.rand_num_gen.state_machine.CommandOp 3 @@ -4226,9 +4244,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedure_control.ProcedureInt + ns=3;s=services.rand_num_gen.state_machine.CommandInt 3 @@ -4237,9 +4255,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedure_control.ProcedureExt + ns=3;s=services.rand_num_gen.state_machine.CommandExt 3 @@ -4248,9 +4266,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedure_control.ProcedureCur + ns=3;s=services.rand_num_gen.state_machine.StateCur 3 @@ -4259,9 +4277,9 @@ 1 - + - ns=3;s=services.rand_num_gen.procedure_control.ProcedureReq + ns=3;s=services.rand_num_gen.state_machine.CommandEn 3 @@ -4270,7 +4288,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.tag_name @@ -4281,7 +4299,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.tag_description @@ -4292,7 +4310,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.OSLevel @@ -4303,7 +4321,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.WQC @@ -4314,7 +4332,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.PV @@ -4325,7 +4343,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.PVSclMin @@ -4336,7 +4354,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.PVSclMax @@ -4347,7 +4365,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.PVUnit @@ -4358,7 +4376,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SPMan @@ -4369,7 +4387,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SPInt @@ -4380,7 +4398,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SPSclMin @@ -4391,7 +4409,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SPSclMax @@ -4402,7 +4420,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SPUnit @@ -4413,7 +4431,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SPIntMin @@ -4424,7 +4442,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SPIntMax @@ -4435,7 +4453,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SPManMin @@ -4446,7 +4464,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SPManMax @@ -4457,7 +4475,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.SP @@ -4468,7 +4486,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.MVMan @@ -4479,7 +4497,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.MV @@ -4490,7 +4508,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.MVMin @@ -4501,7 +4519,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.MVMax @@ -4512,7 +4530,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.MVUnit @@ -4523,7 +4541,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.MVSclMin @@ -4534,7 +4552,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.MVSclMax @@ -4545,7 +4563,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.P @@ -4556,7 +4574,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.Ti @@ -4567,7 +4585,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.Td @@ -4578,7 +4596,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateChannel @@ -4589,7 +4607,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateOffAut @@ -4600,7 +4618,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateOpAut @@ -4611,7 +4629,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateAutAut @@ -4622,7 +4640,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateOffOp @@ -4633,7 +4651,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateOpOp @@ -4644,7 +4662,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateAutOp @@ -4655,7 +4673,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateOpAct @@ -4666,7 +4684,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateAutAct @@ -4677,7 +4695,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.StateOffAct @@ -4688,7 +4706,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcChannel @@ -4699,9 +4717,9 @@ 1 - + - ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcManAut + ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcExtAut 3 @@ -4710,7 +4728,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcIntOp @@ -4721,7 +4739,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcIntAut @@ -4732,9 +4750,9 @@ 1 - + - ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcManOp + ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcExtOp 3 @@ -4743,7 +4761,7 @@ 1 - + ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcIntAct @@ -4754,9 +4772,9 @@ 1 - + - ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcManAct + ns=3;s=active_elements.pid_ctrl.op_src_mode.SrcExtAct 3 @@ -4771,10 +4789,10 @@ - - + + - 4Isb-K1Xr-YTAM-NGto-Ar8O + PJ7I-JoCv-OjFg-VNqC-6g9h @@ -4782,14 +4800,14 @@ - - + + - riqc-7XYh-Xufb-dnF1-Z8Gq + ZME4-BY5b-xYo0-fTsD-w35X - + - 0 + 1 True @@ -4798,23 +4816,23 @@ False - wXfS-FX8T-7QC0-5wkJ-Owzp + nieu-k9TJ-c31P-Pp0D-9d1F - + - BSNu-qUTn-Eawh-CRKr-LvcS + foXx-WB2I-4coD-vAqY-LPeZ - + - CXUf-VdDC-pa83-2V4m-ya8r + ofXQ-0oA6-h3iB-pi8y-09yB - + - iSh9-49dt-spIx-SquA-4ia9 + TFbt-FvCk-Jmv4-kXFv-r19v diff --git a/manifest_files/example_recipe_manifest.aml b/manifest_files/example_recipe_manifest.aml index 8bf5c5d..cc8f157 100644 --- a/manifest_files/example_recipe_manifest.aml +++ b/manifest_files/example_recipe_manifest.aml @@ -17,7 +17,7 @@ www.tud.de 1.0.0 - 2022-05-02 12:08:23.084251 + 2025-10-26 16:36:52.166337 tu/plt/mtp @@ -2355,665 +2355,671 @@ - - + + 1.0.0 - - - + + + dummy description - - 0 - L0kY-IXxe-NGPI-X19q-ABlH - 255 - Tfs0-REf9-4PF2-58Yy-ZYQS + genf-h62q-TFjs-25RS-sReZ + + + 0 + Vs7l-Yn6m-R4uP-ztG2-n5vQ 0 - OspC-VWgs-PG4W-dBq1-n5Ue + BJ4G-8yrI-5Ovc-PTr7-OQdu 0 - Mpe9-aGo4-K1fe-GoP3-szfw + sU4K-yS1j-dpct-F8HX-sZMn 0 - axX5-JL4q-V7Tt-4zXq-5j1t + lzNv-4pT9-ODR5-Bx5R-I2RS False - dDcn-deTw-MHx8-OEy8-nt2p + Zmou-5HMy-9WB2-Nk1l-8Mq5 False - qhv2-7X2q-ZPmU-g6Pe-r4Wo + H6os-yGeY-tEhP-bkBj-cQRv False - 9PKL-OfZv-6VFi-aS69-GogM + Tz0Y-JNce-Je5b-khTU-ugaT False - iJxz-Mtfp-LwsE-CrTR-iurN + qzCk-2fE4-LT27-xVzw-khbZ False - GZP1-FftZ-tTAR-jgRs-Qbz2 + AvS6-luym-BQJ0-69Gf-uph5 False - SIAR-7BG8-w5SX-UhVr-qVK6 + I94s-6HYk-JuBN-0grP-WV1F False - VWwK-BQl2-Rg0W-4qn1-MfSZ + dE7x-8SyE-BUr3-kR0F-EC6G False - MJNt-OuJT-mnuZ-SJRo-nkFJ + PinA-8I0h-hpCR-TGzZ-aujJ False - foiC-8ZRH-lRbs-NJ5a-fWCT + a6lV-5Gkp-MyCI-xQ3N-SQ1a True - xkEb-Uz9M-UPTR-EaRt-HPua + CWLe-McRa-FWLn-nBNS-kXu1 False - oS6B-eHp3-Etpu-zBIL-Flag + v9TC-jyFs-cUXQ-sM0b-RfKX False - mPgG-aVvi-bXGg-D25j-IJZ6 + 4QdE-XrKh-PJXx-krS6-Knes False - ciu8-oX6n-lvfL-Bydn-MC43 + GkMP-RhcY-PJiS-wJ5H-m8Ez False - 1knm-SOTw-NjED-u5tH-31nE + soJx-0rIG-mfNE-oWYx-4DnO False - Ea4b-KXEh-6jow-fvUW-h7My + w1zv-ZRPb-BS93-WPav-NeQ6 False - Ry3C-uOas-bmQf-FMTi-Urdh + VPLm-rRZd-sSgA-gzjo-UAGi False - oOCY-sIA7-w7Py-pasI-DOsY + 8hOl-4219-Do5J-5zhV-1mQ8 + + + LmVk-ox94-8Nfo-fZrd-NyPq + + + LmVk-ox94-8Nfo-fZrd-NyPq + + + 2 + jHsK-RC5v-pZ6d-VHlZ-flmN + + + 2 + hHen-guQ4-2VyC-plkX-Eeqn + + + 2 + YfPo-0yHr-pcP8-6yQE-U9rd + + + 0 + xift-26nS-QJke-X6HI-AiJL + + + 2 + 75SQ-JWfF-i1OP-8Q4b-W8kr 0 - yp76-54Tv-Jmwt-JXLN-aU3d + 6eEY-ZrLE-HUFD-QeoL-f4Z0 0 - XKwf-Eni3-b8FG-ntdq-z8bo + aHto-DXhi-NVAZ-tJYz-6ms2 0 - GMeh-jvmS-7Tv8-0J8P-0S7w + Of0S-1icu-ZBPg-PXup-Rp1m 16 - fyu9-eCfV-eJp1-zqVn-BcWP + U0p9-FwXh-Jpvi-56YH-hOjw 0 - pf4H-qJON-7MEV-UX1q-d0v5 - - - 1 - 3tdg-MaFu-1LAM-xpqn-N0E9 - - - 1 - kr2G-qSEZ-a0xw-Iscb-RwmY - - - 1 - M1t6-Tngk-SbKd-H7OV-0hvV - - - 0 - dfMC-jVc9-HcrX-FRuo-cvIM - - - 0 - qcj2-VrCm-pkE1-S1ni-go0R + kr08-fTHG-kFpA-sNOB-wPvQ - Y9nh-ZYOj-D13L-f3Lj-kD7B + LmVk-ox94-8Nfo-fZrd-NyPq - + serv_param_ana - - 0 - JPjY-0FSB-QNFS-QFl6-3pVg - 255 - fmsV-WwFN-3Bwx-qRcz-nqst + GApf-huxC-DK8s-wfBU-Yju4 + + + 0 + FyvP-H5YW-NIpq-aUEm-P6C7 0.0 - iRs6-5zFU-Ky4G-c4m5-WURO + zL08-zxdy-Ioz6-o76z-iWbR 0.0 - pebq-PRQf-6ujI-skSX-tNx1 + bIwS-CrsR-uIKx-jb3k-XmWO 0.0 - 6huD-ekJ6-0gD2-yRlA-ceF1 + zZe8-wC6y-LbvT-Tl7K-3RZL 0.0 - 8Uo4-uwgt-w8bB-3W4O-iGmt + Play-u39X-AO04-5ULu-BHTx 0.0 - irH9-7Zwc-vCYB-zBJN-C0LV + uSFE-Fw8x-nwFU-1rxS-8jrn 0.0 - dxzF-0ncx-tDV5-8e9R-eZJK + c9qJ-hZHK-gTY6-PfhX-aYUc 23 - BUsW-jvQT-X9Pb-lzDm-SQbZ + Avi0-E5XF-sb9f-wxEI-2yBo 0.0 - K5pm-Vah9-1aO5-ZeFy-DnKZ + qG7C-tdqc-DPCA-psB2-9YHS 10.0 - dUsb-O3mx-pfHy-8QLJ-3vsY + HLob-SjsE-6hlg-vHCd-6j3S 0.0 - FHzZ-x9fe-6PTY-u4TY-VjOq + 7vXE-QLRD-QLUm-wmCQ-Tk0R 50.0 - SsmA-0xu7-OC2e-Z0sY-HZ5v + QZRP-Vdhc-1jKE-pXom-h7Lt False - xTZE-Vagu-q5Yp-cSIT-Ag9q + aPrQ-FG0w-u4sg-SgPe-TzuC False - 7hTp-7oba-w4Ec-P1Yf-IZ96 + EAmj-RIep-bD7T-BkXp-bgIA False - v8K2-M8DC-FBhw-Oto3-pOQ4 + m9n7-bDAM-1CQr-DIui-ZWcF False - hEIX-WAhR-1bpx-oM6n-JmTL + evV0-xIN9-dsxV-DTkS-yAf2 False - h2QL-uydK-d2U5-T0Lr-ouZs + BY81-HZrB-jSob-6lQp-nYFI False - E4nr-cpkf-LOh7-XHv0-JHO7 + JTNE-FaIE-eOrz-jVwc-sdkC False - 6ies-2HgP-7r5s-fZMi-5Omz + Xno5-L7YN-tHkQ-nR3r-nm5H False - shDJ-HKBc-KUnI-NSfU-Jlwb + hWBe-aG2A-yiQr-ea0A-crsm False - cRFx-7iso-tPwQ-BDr0-l1mv + V5wu-Qw1s-xlNL-vYaX-2xV3 False - xmB6-nESH-I5kz-sg4u-OSgh + V14s-PsTI-Grm3-HYKb-KMDu True - WS4s-AY8h-N3w7-fp8L-6kpd + OR9c-QUzJ-pdE4-7Hut-ZDCq False - wn0X-f3wJ-T3p7-zg5r-dpE8 + xyRV-Is0C-6BVI-zmQt-BFQz False - oyLI-g7HW-toKF-uZjL-Da7r + dyPt-ew5R-MWiq-uWpC-y3JV False - N20V-lTUo-eG8E-aNFz-2Gm1 + peJd-TSAo-Dej0-hHst-dIZ5 False - J27M-rlER-Vyjf-hoSJ-ZS5d + 80Et-y30T-6ZtP-z0fq-KUV0 False - r0BJ-dqRM-VaFn-AfZY-Fecb + u4fj-4BfI-rEbu-t7w4-GKos False - ja4G-Noxe-WVS0-7C8I-uoW5 + m386-UtcD-oRId-rW1M-CYWs False - YXAe-LMhT-QbTD-uwDW-Sg5q + Gf4Z-Qg3l-gTvn-bTBf-PWME - Gq27-6Jon-hJdl-fk50-laR8 + GdrK-5EV1-WNOu-h9Ee-YZb2 - + serv_param_dint - - 0 - fquT-7mVJ-sRmj-WGSa-5bPm - 255 - nA6s-kPNm-sISy-nCwE-EoAy + GHQV-RqiO-KE7b-D6dZ-ne8F + + + 0 + PdoL-L2rh-EbVG-OjuE-zReA -10 - neCX-SEmg-ZVcg-YDuH-updw + 2WX9-7XaK-9ufG-h5X8-aO2N -10 - rd5Y-6FBX-Frx7-RS26-mQVr + RCOB-rfCI-cLws-obyM-jRas -10 - yWwC-hLOv-XlOh-5JwQ-rxYI + db4h-04XL-p5Sy-zIOY-SY4a -10 - AQ7k-O9Uz-1WnV-hGOo-CyiA + 4bDr-Ji5C-3Krz-Urp4-1fHd 0 - CIVe-oZ13-HwL6-AnkN-9NAF + Skb4-vJra-qnW0-zGaW-Rki6 0 - O75J-7Xn1-BZqY-nxcH-gEzQ + hJAy-RNvW-ZQt9-Lv12-QXtL 23 - QF18-fDTe-Zkx6-CRZ5-EG6R + RVw0-gnct-gXSp-KcxD-ZWtn 0 - l7PL-lhtp-zDIs-sUMv-hDv5 + 9JLk-Xhrk-ysIm-h82U-vRLE -10 - 0tdD-mOhk-543I-HUfC-jy5k + 1VTY-31ot-3zWU-agZj-4vbX -10 - SigB-zxek-tlPC-2A43-o4gl + p4JO-8PQZ-f3oG-qaN2-lzGV 10 - 2OjJ-N3nW-nwVo-4bsg-BSCL + jhtC-uUJ8-2cMD-pDke-aR6v False - YuIL-si2Z-TmME-ENut-hKFT + PZnd-XPUv-gvFs-bJXh-IdiT False - yIiv-msaM-rvKy-Si8H-6EY5 + dyPY-30jy-xos0-7KgG-3RDl False - aNUj-4I1r-RvAz-10Ln-90Zp + F7Vv-IxP5-7ktG-Ndmc-0Sbm False - csV9-aJfQ-QLP6-YlbO-2lOQ + hYKf-ENJj-7Lo8-rlW9-uACi False - IVki-I1Cm-oKBm-T6U5-9DWq + F9b1-ie9j-x2zU-DUnN-37of False - QRSb-ymPi-YNq9-ms54-kIU9 + E8eZ-FiGz-37Do-KhxC-QPIm False - QkOf-Uwq5-Lz6T-q53y-gMcv + Ctq4-c9JR-Xa4M-4xcO-ZaUE False - 9PWH-bjz1-vI3u-FVp3-V7Tl + 6sNA-Z2hV-nUMc-kUop-3ViZ False - AUr3-gzZ8-g1fA-TJtk-qZYH + lobJ-yuBi-MPDd-ZJYX-inSv False - 3o0t-cVqF-pEIi-yjCo-ur2M + mR35-01ni-juc2-w2dj-SkX0 True - vuER-Cboa-lCYb-6G8W-mvqK + wqBT-a9QJ-aGOV-5yk8-Gshd False - 4iMC-4OIi-EHk2-vqxs-pE0x + IbOT-Pys6-O8ZG-7YGy-chmM False - AaoM-y03g-SjC0-7ZX3-xWwF + lWh7-8tB4-yDKE-1x9X-qmCM False - Jxwk-ib6f-Vuwo-frZb-dcqe + xjkr-vWbY-9T3m-O0QE-Yabs False - 3Yxu-eRj9-IqoZ-RquC-7xOb + 41bQ-p9h0-SfIA-XkU7-2MSW False - wdus-nC6D-Ipbo-lPa5-sOt1 + ryXe-9Iwi-zrYv-hT79-svV2 False - rVB1-QtsL-8P2e-WTmb-IBrs + zQeE-uy8d-FWLu-mvBw-YsRz False - gKNR-LjgH-Pdl0-i073-4hEI + qAxF-mSiG-twLx-caiT-YvZo - OWoA-Ipu3-LJKp-F9Ee-EJh3 + 3hLT-t2Ub-ebuy-7szi-zRQ9 - + serv_param_bin - - 0 - YuW0-T9PM-yx9q-OCEf-uBhE - 255 - ATam-ut2Q-iHEp-ZBHs-qbk9 + 2eTc-ZAcE-fWg8-NH5B-Ojg4 + + + 0 + xKTe-LF6D-RtOP-Grkw-GYfE False - yVeC-u0bR-TMsa-QLbg-rN38 + UuwC-DjQM-dXbG-M1oX-6ZvD False - zK6j-wQYp-4o2B-aG2n-ZakD + iFvs-kSQG-zvTs-JIpg-vnR2 False - 3ajA-JgP3-Zfsj-2o4w-yhfK + lb7y-oELS-6MsC-zDBl-R8hn False - Yjw2-NA6m-iGNY-j20M-4qkJ + OTpb-oM6H-XTAg-fnTw-v1b2 False - uP4y-vVGc-Utws-gAV4-Mxfd + xqXe-m1o0-pWcr-lTtz-hqx6 False - hQsY-ZsKw-7TuX-KykA-tunp + JOjh-YkdG-tE0S-mZGS-u0AW state_0 - 93Aq-04E1-4IUl-B8F5-wRfm + u946-wREo-dm3e-Nd2F-RvF1 state_1 - Bwlg-NKg1-vwV2-FYa0-WLi4 + cP6A-2zLA-XCYl-MWEu-UryN False - EVwW-uNSy-f7mP-w5aC-4qNg + mM1c-7UQs-4VO2-ryzI-5CHw False - ndGT-g7dK-rQ12-1CxO-xqUi + B4Fe-IgSb-IxWY-8df6-DcIC False - kIlC-usSh-aoyB-tPry-a3SD + T8D1-6NRK-w4Bz-7Xq9-fyWI False - p5xJ-4Po8-qw3A-azXf-WKrQ + 7lmz-p3oj-zi0n-DZVh-fjhQ False - LJuj-r65m-2dfF-oWb0-zFUO + Kxp0-zBw1-hxHc-87D1-yLfT False - 1tzV-isFY-RKpz-nDUI-fU2B + 08Cg-imvM-wpY5-cm4n-Ehrk False - JhMs-fB8j-R7cG-Ufh6-R7As + uij2-VJkY-1gsM-aRuJ-lxcb False - IzaA-hyKo-eWmz-urJ4-cJKV + BPc2-VFsJ-dIZV-W1AF-ZYhr False - LhgO-aBb5-Yf9s-M9XK-ltHT + DWgd-8JI5-q3uc-ZL5j-R3Ea False - GMLQ-e0N5-UbfG-C7sA-iMHE + s5oi-fpQ6-sJjU-OEb4-VSWk True - PY0b-8peT-uJWy-HRyU-eVYF + AY4N-Od0u-atqI-cREd-fDHj False - 5AJ3-bcrG-1VyF-mCIl-bsox + uQer-E60h-6OkA-xS0g-LITE False - UYsv-UDOI-X75G-u8LF-YfJx + NMAL-Ur04-pWNd-zTvK-N2H4 False - 5riE-GNQM-bC34-EavX-g5yr + VRGD-1ntN-cyZs-8wCp-ct7z False - JrHy-Sru9-c9X8-o5zP-9p5z + ITR2-35sg-LVvr-wLpe-2wJi False - Z1zd-XjWc-lfeN-sUD9-XdtO + a2Zr-vxOc-CJBL-xGpA-6e5f False - lk8L-cn7t-myZk-f3p7-G0qj + 3qOM-CdmB-g4MH-2hab-qzH1 False - LAEB-97Eo-3Gyc-oM1G-CU3p + TdWv-lZ0n-zxnk-hM9f-kpfG - jAme-IlwP-HK0v-TEQU-0QHD + 40jA-EyKs-T4e3-fh2n-wdNX - + serv_param_str - - 0 - UywJ-ecH8-j85w-LOtM-1Fdf - 255 - KkMe-lCzR-xSaU-Ir7g-3Q0F + WeSM-q1ca-DAVK-1cPt-6zCB + + + 0 + DFji-qJG3-cu8z-p9Mn-ogtm - js81-CaIQ-y4s9-0pXk-TLxA + ZIAn-26bB-bZrh-Fx8k-2TSb - fizB-4r2V-j2xp-1GTP-6tpV + cGqW-ZHFD-o2AV-uzgU-4ntH - JZqS-3qlf-gEn1-2hj6-vV8q + J2ye-3d4m-umwx-W8eG-wHSk - URqC-6b4U-ydtD-pmUx-PCst + Ewxi-3pjS-pdS3-hNxH-prGH - q38D-ykdK-H1Ky-FANX-zryE + N1qL-CyUI-hUcf-J8R2-xyGo - RNpU-3PnO-gtrn-gJ9v-jUls + fUGx-uNHT-1X2t-Ro9c-V6zc False - zmFk-SAoK-l13o-COE3-DG6q + A1s0-ud0c-127m-M1fZ-xUEk False - AkJC-kGcB-9WSA-lt0E-qcyz + g4W0-kyKO-sD5l-J4qZ-ts0w False - hrLI-kbgR-xmQk-nDhw-tNLM + 3kHT-JyY8-TuKb-BAR6-c3Zl False - 7Cj3-VREJ-0hqz-haxD-yOJx + GWox-Np5Z-nKUY-TYXP-YSXv False - Nzaf-m853-jP56-gCar-2iXn + n8ys-JqZO-pol1-KeHn-wlUq False - 7Ijx-vkMq-7UYa-IBbX-ASTv + fcO4-4tHO-cF5Q-BTNg-73Cl False - sd4L-OL04-WMfi-j5CN-PpFx + g3ho-3x4U-1URr-CtzT-YnJ7 False - N0hU-oNZy-u79s-uaWO-qV56 + sKYJ-tUGw-GkOZ-S5Rd-rlPX False - gNw6-0HNz-KCP2-bF8x-1HqI + ZUHk-F6oE-uk2f-znL9-zy5o False - e1Ib-tiKv-v9iy-A1bB-CVFo + ZfYQ-d9El-PBcg-umDl-rN74 True - ZfFi-gedy-m6MF-r7nL-TunL + x0UD-w0IP-0zQX-lciJ-pMh5 False - qFbt-fi3v-go5M-h6Sf-e0Mx + 2X8A-9XEh-SgKM-KN5I-joBD False - aNVm-XjOx-QGkU-ksa5-T3c8 + Y0Qh-t4bS-YrEG-e1Sp-1p2l False - 9DkX-dhM9-c9Kb-LMRm-e2Sg + uF2R-fXST-uCUa-HVXl-AXq6 False - jiYD-MZeO-ypxF-GSYg-jWek + 6n28-bu0D-2T4i-M1XJ-QwFx False - XFyE-Ul3R-jHv2-8KQ4-HF6C + W376-j2Vp-dMe1-FWXq-pILf False - 17FP-yZCF-qOlj-iEu7-hJaI + BGIz-X1xL-N2rd-Wj2z-L6rB False - 5xCv-7lNW-ztbZ-c0jw-Pmhb + JbHV-E72I-OeEY-UIAV-tGwR - ODP8-T3cW-ENLa-p2zC-HqMR + LBGP-5jWD-Fmus-qnTf-inJP - + proc_1 @@ -3022,14 +3028,26 @@ 255 - mqx0-CqXF-kOFY-mWFA-q5lo + oYST-iFpN-Wzru-6krR-j7us + + + afbI-LaKY-PvmG-B31c-kx5f + + + afbI-LaKY-PvmG-B31c-kx5f + + + afbI-LaKY-PvmG-B31c-kx5f + + + afbI-LaKY-PvmG-B31c-kx5f - GRDh-omR5-xMme-LlJA-dDx0 + afbI-LaKY-PvmG-B31c-kx5f - + proc_2 @@ -3038,14 +3056,26 @@ 255 - lpRq-UPpj-BMqs-bs8G-q8YZ + 3ir1-kfnM-msN0-50mX-vR1B + + + EF0Y-eXga-TsEn-lV6w-auQB + + + EF0Y-eXga-TsEn-lV6w-auQB + + + EF0Y-eXga-TsEn-lV6w-auQB + + + EF0Y-eXga-TsEn-lV6w-auQB - VELF-Bx0c-xdL3-NgXS-Zm5T + EF0Y-eXga-TsEn-lV6w-auQB - + proc_3 @@ -3054,526 +3084,538 @@ 255 - 1iew-zo19-sfmH-zeja-WoFV + YJhs-YdPn-3Snq-AagH-J8CQ + + + 5Xic-XpxI-c8wZ-f8kl-axMW + + + 5Xic-XpxI-c8wZ-f8kl-axMW - m1IK-dmiM-l91o-VD2j-JWqQ + 5Xic-XpxI-c8wZ-f8kl-axMW + + + 5Xic-XpxI-c8wZ-f8kl-axMW + + + 5Xic-XpxI-c8wZ-f8kl-axMW - + proc_param_ana - - 0 - yk3H-sHAw-41tx-DFxZ-N67j - 255 - 0zsP-ke6M-Wh5I-b4eF-gn2m + gMYh-X3Uc-gGux-SEPU-iPm3 + + + 0 + dFP2-4UTH-dzIS-PZNm-0GET 0.0 - zZbq-Io3R-hV2v-VYhk-SUKM + aYM9-PDqB-Breb-JxaP-PaMy 0.0 - mWVd-V7wa-n1PU-ogx8-1xVm + Cg5s-VgBJ-DnuQ-Xzkm-Y3yg 0.0 - SWhU-P6bF-YVar-qmSZ-BS1M + Okvm-SJbi-uAkP-HmdI-3eiN 0.0 - Glt6-gS2H-TBGV-3HJQ-Nkf1 + a1Lx-SixY-5gfX-8wcS-QbA9 0.0 - ni3a-Rtko-kwAs-gqLx-2sQl + erHp-Ypf7-Cysw-DicI-rcQO 0.0 - VZL5-6lYy-sd3Z-nzox-QyL6 + NAeh-Dy0p-GnI9-HczM-HYSa 23 - zZ7p-hZCO-aVwF-4hw9-ruL1 + XOTz-hEzB-49EN-q4LZ-JAxK 0.0 - BZp1-vyxZ-dsqx-Sp2f-ioJE + m5CL-4gXp-odKB-eqE5-9lWm 10.0 - R6un-ZQnW-Esl1-SMbv-gzQj + o29B-O8Mk-OoU3-LmFy-snxj 0.0 - jxh0-8QIn-5ekz-kxie-6MPA + F7fp-dakY-KSnT-rgjq-8frb 50.0 - ArTH-o2WC-Qpga-yHaU-m4SN + OvXr-s5wY-6pqP-d5vw-3rhA False - 8DrJ-3lGr-sp4T-4Fi1-FEe1 + QOvK-4D5h-48Vd-Rb82-RCrN False - UJD8-lDbm-qiGP-1b9v-E1ug + zIkx-NY2C-Q57V-RGwU-5IyH False - dpZX-i3IY-fQ3q-vm6x-ZWY9 + XVCZ-XJv9-ENLg-vhy8-ETKa False - Lj1d-eqAh-ruy7-VhAC-mYOP + Bqdv-1Jer-JA8V-YUh3-m3SN False - AOG3-n1io-83Cv-8yms-DtaU + yiHe-W2Dg-CTsb-cK7T-YerW False - UjKJ-iQ98-PGHa-OJFY-yRUj + Z5X3-flmJ-xj5Q-pLx9-aqH7 False - 1INn-Avut-a527-QUoD-3FDM + VpdL-epTM-ChY9-sy1u-msML False - TS6V-iKIR-zXjw-sX7R-iMYT + hrb1-3Dqn-RcFT-mY3M-XRxU False - 4z8H-w2ZJ-LoB6-AiI1-pAjc + 6boY-BSwZ-vaOz-J5sC-MAhk False - tcLx-jpUk-RPT7-FP3n-W2BE + v0uq-JyOa-d5Ck-hDbk-izlX True - LlYH-Gftq-lcRk-eT74-Xk1v + D0QK-dqR5-B5sa-HbdP-cidQ False - o127-Zk3M-gAMV-W6i1-jrFD + 2vUR-Siwr-S5Bt-9PTw-h4Y6 False - RpdK-dnrv-Oc8F-NeDB-x1sH + ARQY-kHKQ-CRuH-T2dn-NpRO False - WcA5-lYbd-b5Ki-i1O8-tBm3 + VIEu-OruM-a2Ai-LBD2-cnTe False - h01x-yBn7-W1aG-J5U0-Chy2 + frqz-c0eO-1Son-SbzV-3C5z False - wQU0-WV01-5DPb-TJkC-O6YL + woyO-MflU-RU8w-VZe6-0JhU False - 49JS-1tmP-YcDK-ZgDq-evo2 + uCy5-gWIJ-5Uvg-xn8B-1l7U False - lI5S-31zo-OPAT-yYci-adkR + Ub8H-m97v-O9Bs-GDX5-kjmP - AgfH-Mi2s-eFEB-HrZM-2ETD + pjZa-AURp-IfsS-Enh7-OlSy - + proc_param_dint - - 0 - l7is-9Ume-15ec-bIGe-p3m7 - 255 - D1uU-pdJ2-H9DR-K9rD-vBom + LE7Y-q07c-1bWg-ZyOm-GxQZ + + + 0 + LHXF-7gxs-67co-M5x6-2G3V -10 - TFKm-LoJ9-qmkb-gx4G-yRIS + o2gq-lJOz-rS6e-Ihtc-k98I -10 - vbD6-rMlE-OjyG-0Xqa-bPLd + PYKI-7SeT-DjQo-lyQ0-btvd -10 - kYbW-3l4G-vypw-kdZn-ZL7d + qpfz-QqTP-X9iG-P7Bt-f6vz -10 - 9bly-80ro-XU7s-ipZO-iZIy + HuVg-iQFe-Nj58-dWbs-LhIZ 0 - vhzr-zHqb-Xfr2-RqbX-NbRq + xgP0-0RI8-tNU9-dXo4-hYoA 0 - PUT7-1ku6-DH0z-OV0c-H7b9 + sgMJ-otf3-PvmC-uAGD-Y4qA 23 - lmY4-u4zp-15N0-6hWS-eD4U + K3Qu-Xnge-LZTy-Hgvy-uOqo 0 - aD9Q-BbZS-WrbP-pVcH-28rp + Gish-Tj5i-5qN6-TbmY-9ShJ -10 - 6dN4-PEiK-JobQ-U91F-zOlD + 4Qq8-bG8K-7BOM-BTbm-P6yo -10 - NaFb-PnvJ-T5R4-vTqK-GUsm + lnEx-LSiU-cwdj-25kL-X2hS 10 - C8p6-UPCI-XZI8-PXTY-1nJR + nFWp-ZeN5-XwtW-Mehg-oGep False - GZtx-OMG9-L1Kq-RMPU-1xQI + 0wxR-PUby-F62t-5Yhn-qPJ4 False - DKrW-6uvo-XBct-w8Nf-FbuL + U3gL-FY8Q-7i2m-6MBE-CXk5 False - gDJR-157i-tAyw-FKzn-4VYr + gl0P-jl9h-tnkr-fTW7-KOzQ False - lLxZ-hapB-u3jP-ozmJ-6rA5 + K8UE-IsxT-E7Oy-GPym-IURh False - G20C-oz6a-DZQa-NG9K-ibrg + CuWH-7ZnT-Qb7l-6kSt-xlKw False - IGes-b0Ch-CJ2P-9VXj-YvlP + BPMH-Tmwu-OIqd-mAqQ-Fc9g False - kZ1g-309J-CZ9S-ezxb-xUGI + mq9Q-KX0U-0lgK-90we-Fn9z False - fcTU-i6MQ-5wSF-Gxrf-G94w + mdhp-EhkX-RJzh-8p1Q-fQBU False - 0Uuo-wVuo-nTCH-iz1V-kWhq + La5H-GLe9-wpfU-QDYI-fe4p False - HhLn-7rzM-Ss38-JkXf-1gH4 + coH2-13jM-mB7x-3jQg-c8gv True - 6YiK-6Cda-p1qn-Dil2-aDIK + mC6f-p5Ku-JuWX-lzvP-XLcd False - jLeO-Y7pv-IDlK-Z5I1-vJXI + N0zU-TqIb-dxT8-e9od-Yhct False - TvV3-u6Hw-LuDW-r9Pq-C809 + SFdZ-gGsJ-Jdyk-yevW-VKjm False - 4iNa-cexI-x7Qq-uqRM-NjkG + mBGd-zpLn-tfGn-ZElQ-Z8Mo False - t0xq-GaUI-vdHx-0NKk-VnKB + tPQA-LIQb-6EVu-3Nhl-TBZK False - 83dX-OfWV-ZCVF-ZGOP-cQsU + SIY8-0wfW-1FNl-p7Ty-stO2 False - lXvV-7dOb-OH76-M6YS-hdS2 + wbJn-azyV-mxIl-mZiA-g5fx False - grZ6-Wdjt-YrXa-wkPm-hRrE + 1ptj-PcZ2-1UuO-YzNj-SjdZ - hADx-uTnV-DCjY-PpWj-JqmR + 6dzn-zCxM-orJC-g0zh-36rl - + proc_param_bin - - 0 - 1u9n-dHmJ-2Akx-EyoR-B1kQ - 255 - Yros-98jH-gMLK-b3yt-XRMT + habp-a7nM-N79l-IjlP-EA9F + + + 0 + 9BSg-rTfR-amPN-w0zQ-Diy8 False - h8Gy-yZzn-Yry6-zd1U-acZz + MdTq-HXfh-6qyG-jBgX-YNpI False - KWGd-F1SV-ynV0-gXvk-kBYi + uf1I-y1Ns-9T8j-dw82-y3lM False - aOFx-3Mv7-raeY-8eyF-x4fh + EuDj-gXho-bvwO-Y0NZ-0yBY False - SLrK-PKOn-Mdkr-kVL3-HhVE + CM9w-VIxm-a4U2-EG2m-VxTX False - HFBj-yEH8-VB62-iCgk-dBwx + qwhI-2quv-NJ0O-yFbC-IG4n False - FkLw-p280-5SFz-QrKu-spva + VXJ5-YuPr-HP9W-zdnM-oAtV state_0 - 5RQX-498X-3dpY-Pft6-PhYR + 2eMG-BJC3-UvZA-nsg9-4rAb state_1 - gRwL-Q8kW-iByk-ftVj-F6XE + AzO7-QsH6-1bTA-tBV0-KQYJ False - n76o-UgSe-rLIK-VblP-cwlX + Zesc-N89t-kZit-B38k-yFmu False - QMVh-5dEA-83s0-p2is-Aiwv + NiMn-bYt8-WXtm-dQW3-UGkQ False - Nzgk-9lgC-0msW-yTVh-dEyB + qnTl-Z1Sx-xKbD-UeIM-1fhJ False - 9vyZ-gx9j-xi4I-Voja-qe85 + Brw5-yUv4-NXSi-4gyr-47yT False - Oz9M-of7c-wFpi-dKSx-1JHK + UM6I-yHsZ-yAni-4Rf7-hXYQ False - 3G9Q-gqfw-jcBI-8Q1o-jP6c + mWMX-gdnE-kQAv-2EjV-cPrx False - aBz0-clnW-H5Yr-1mo5-yeHj + bPAM-lhZL-xeaB-FScX-CQkp False - rAyT-quza-MNks-wXmk-B7km + 6IvG-iYBN-LpJ6-hLZC-Wq1Y False - cUDT-Lo6l-wjMT-V4jh-9lNL + X6Uo-Gx0D-qeST-RetK-XezU False - kjNy-mYjK-PTZR-KfrP-hOAf + WYeG-ORXc-CfpL-57gR-x8ML True - DmJT-3Ur4-c1HX-wRMi-1o0G + Gq4s-Bw1T-2xbV-JZ7F-w4p8 False - f7cW-5aJB-2Pxi-rGsj-uLO8 + zhOY-hJE2-wsPo-o9VP-mtBs False - nSZF-WdSO-slZp-6OHr-tkGK + La16-67xs-Wdnu-VLWk-psLn False - oVLH-GTbw-qDRW-GBCm-7PGm + Nwzd-NJ6L-Wsjr-fq5J-uOI5 False - xLpn-1lqf-8qsD-2dmM-ukfP + yRlV-htyq-FglA-CNEe-9YDb False - aJz5-yGNv-Jyit-ANcj-XIpS + 6RfZ-MmEf-13jn-KxAE-IKJ0 False - ubHP-Agrn-bJ6K-QNi3-Oj4A + 7L8C-jNRB-2r4x-iMwy-eW1t False - xlAV-kfXi-XKgJ-NHad-PNwW + Yxic-ltnI-3Nfk-yxzT-0ftl - lg0V-kOfW-Wrmg-8xBV-xr6O + A83w-Kpvs-lpwj-EKh0-8MmX - + proc_param_str - - 0 - 3Tvw-z8UZ-LKmv-dH7W-pQJ8 - 255 - GRer-y1o7-DUWH-9WKE-3zgM + Z6FO-pYzX-xo2w-e7y9-LRcz + + + 0 + XiTc-0B1A-wO2b-jPi7-kMQ3 - 6Ouz-hGDl-XkLP-VFmt-peB3 + woA4-QUws-9FX1-XDa0-QJw3 - jk3T-9gIO-vDyl-JH7v-ZF80 + 70fZ-c3ua-xN12-okTc-PVgL - Vasm-LOhP-72Qo-ygZX-rkv9 + Fgtj-S08Q-CfnF-dvCW-ZM1B - upFg-pGiv-YDhX-qkBW-cj0C + EBZv-MdQm-IPcj-S2QK-HRMd - KwkC-9eja-0hw9-o9YX-wgDS + sVfb-jyYU-7AtU-CURh-9ClY - nDw2-p85q-MOgq-Iz7Z-lI7y + XVRr-H7yG-GYOX-4qBL-fnUW False - 9iZW-ExsC-U3Yd-AREk-HTaJ + t6JN-2UgS-eytU-IVbJ-LPru False - BmwJ-c3MH-Zl7d-yt1T-FYSh + Y3OV-TWy2-iebA-129D-RkT8 False - lWwH-aSBq-e6l0-n9wl-mEHq + pUrc-QZpd-Vug8-iDO7-WiXu False - Q9Yl-NcuP-ceGn-gTCr-T2nI + 4bKt-FYbO-vmsu-90uA-zIpf False - fOu9-iz1t-TeLv-48s0-MUH7 + PXiR-sd7h-YFPW-LETC-PpMU False - Vo64-oC3X-tq1s-h3bZ-7wKQ + FMkK-7tSw-Zz1V-49Gg-X48H False - iTDj-69yU-0N9k-SBuN-VreJ + 6g08-aZfv-Ye5j-AvGZ-l7Oe False - UY4h-EOnR-tmSN-NFb9-WqKk + NL8w-CVfj-b01C-CEeZ-rZXF False - 8PxM-OpT2-2VX6-78Hu-XeJs + QlDt-a64n-1lSj-TdWi-IbdD False - Q4hX-wi0U-Z8Ok-35sX-EHmN + vsAw-uGqn-oMZR-ESjx-sDIG True - wStr-hpaG-bzAH-sVrd-s0XA + u4Cq-Pu2Q-Iv2s-kq68-FTD8 False - ZUCb-fyBG-4iCO-zqOi-cnqg + Ki0D-i8Mx-irs6-uUoL-gvi9 False - alWY-O7gs-8CEr-KM35-vPpT + 6YRb-KLuU-wZMk-1rDh-N31J False - vFWa-jx9w-DY3I-m9eW-j1kb + PAMn-qBZR-kC3O-P02y-VJsi False - DWjA-0FeB-WyVs-Dc0a-sySI + h4Hv-kmvB-XEeb-GARD-WsgB False - Mmkr-CzY2-42Go-oJdt-tc0T + 1NXF-DU5b-hrLv-EjZz-rJAQ False - e2CW-FEdn-Hz6w-nI4Z-My9V + Xhe4-ky8V-8tUQ-tL8F-GZvI False - GRgL-uiTg-p0Yn-v4dZ-4hzG + cLwg-mbzt-QRZ6-zZeC-xdbK - waWs-Acno-DAz5-cJXs-x8AP + KwHW-OJDm-Dkzy-9eW5-nURv - + proc_rv_ana @@ -3582,34 +3624,34 @@ 0 - XSz6-HLGo-THew-4xDM-fCjT + uVzS-4Zzt-LqJR-FMdC-ql6n 255 - F8SU-6nsG-CiT6-RmnS-Q93T + HPYN-7Qwc-J7XC-EUaf-aG40 0.0 - dAIY-SNKM-oGrw-MOpH-BSEk + cfZY-WDNM-9rJB-UWbq-atXk 23 - 34Vj-ywRe-KX0W-vPCj-tdND + Uhfm-KEmo-DNuI-WG4s-PHmi 0.0 - sM7k-wFnQ-Aogm-Hv6w-Yrt5 + iDAH-ZOLj-Cqt0-mrdc-4uLv 10.0 - nKNk-J3Uy-WofM-lAnC-Fn5X + OcoI-SOWl-wDk4-7CV4-2gfp - kasC-KHPD-7qns-FBky-Epje + Wu4i-9Z6g-Bfcb-bypW-1QrJ - + proc_rv_dint @@ -3618,34 +3660,34 @@ 0 - 7hqR-xdeZ-58OL-0LoD-mpU1 + qLoX-6iIv-Nk3y-ZHYz-p1nx 255 - IVP7-EfP4-qQJi-AwRi-R6co + zMg3-uO1H-vHFy-6EjO-bHKE 0 - 28GJ-cRnb-YzxO-AylR-xQjP + hV7K-MwKb-jHNJ-ToKs-ZBf5 23 - tCOq-K4PY-tsRz-4kum-k9QF + MAxO-JXk7-0cT9-Wc8t-qYME 0 - 3t0X-W6D7-BjlL-tIgC-hvaq + NwE0-uENO-yVqf-N3az-AUG1 -10 - ONAz-drJ3-ldtq-McxB-UoAw + aOb3-RqJQ-DRCU-v8Ac-4nFG - xtDa-fBDy-q2tX-bJE8-3pVL + 3SJ8-HXgp-VLnX-dLVW-zx9E - + proc_rv_bin @@ -3654,30 +3696,30 @@ 0 - f1Xu-GBe4-HlB9-HnG2-waZM + zgb8-zswb-BtF5-VXwb-jA2B 255 - MbVX-Gpz1-cOVb-tZVe-yT7P + YI5q-9NhS-dTDp-CKIR-Rq1n False - fLAz-IbPo-NJZh-TUJt-NRTi + wc6B-pWd4-Bb1n-1hpX-Ef7T state_0 - Zq2m-HN78-eVXm-6xEJ-olt2 + vtlp-N6dZ-Ixp5-qW2w-Yfq6 state_1 - HZ7K-vNCT-dG7e-7W8n-ECKe + uSYI-6cpb-I3au-cei0-b1EW - Purl-2bCJ-Vw1J-YhfX-Cnlw + SDOb-bGH8-EYCg-Y4My-oM9r - + proc_rv_str @@ -3686,29 +3728,29 @@ 0 - R3ZM-4dxb-JS8r-78ky-OMYr + XI1p-QYR5-xK2I-N6WU-tSKC 255 - AVJS-7pnJ-qYwJ-os5f-nPZj + R3ab-8rn9-9TEk-sODE-eZqD False - 5NYt-15CH-DGoh-gxPj-QT5H + v0Sk-qK9n-3tNG-f85R-xYqk - QsWD-eUKw-j2a6-0p4m-SkU7 + yLb7-mbNB-C6vk-bsRI-B9Cx - - + + - opc.tcp://0.0.0.0:4840/ + opc.tcp://127.0.0.1:4840/ - + ns=3;s=services.dummy.tag_name @@ -3719,7 +3761,7 @@ 1 - + ns=3;s=services.dummy.tag_description @@ -3730,9 +3772,9 @@ 1 - + - ns=3;s=services.dummy.OSLevel + ns=3;s=services.dummy.WQC 3 @@ -3741,9 +3783,9 @@ 1 - + - ns=3;s=services.dummy.WQC + ns=3;s=services.dummy.OSLevel 3 @@ -3752,7 +3794,7 @@ 1 - + ns=3;s=services.dummy.PosTextID @@ -3763,7 +3805,7 @@ 1 - + ns=3;s=services.dummy.InteractQuestionID @@ -3774,7 +3816,7 @@ 1 - + ns=3;s=services.dummy.InteractAnswerID @@ -3785,7 +3827,194 @@ 1 - + + + ns=3;s=services.dummy.op_src_mode.StateChannel + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.StateOffAut + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.StateOpAut + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.StateAutAut + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.StateOffOp + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.StateOpOp + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.StateAutOp + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.StateOpAct + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.StateAutAct + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.StateOffAct + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.SrcChannel + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.SrcExtAut + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.SrcIntOp + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.SrcIntAut + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.SrcExtOp + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.SrcIntAct + + + 3 + + + 1 + + + + + ns=3;s=services.dummy.op_src_mode.SrcExtAct + + + 3 + + + 1 + + + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.tag_name @@ -3796,7 +4025,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.tag_description @@ -3807,9 +4036,9 @@ 1 - + - ns=3;s=services.dummy.configuration_parameters.serv_param_ana.OSLevel + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.WQC 3 @@ -3818,9 +4047,9 @@ 1 - + - ns=3;s=services.dummy.configuration_parameters.serv_param_ana.WQC + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.OSLevel 3 @@ -3829,7 +4058,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VOp @@ -3840,7 +4069,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VInt @@ -3851,7 +4080,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VExt @@ -3862,7 +4091,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VReq @@ -3873,7 +4102,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VOut @@ -3884,7 +4113,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VFbk @@ -3895,7 +4124,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VUnit @@ -3906,7 +4135,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VSclMin @@ -3917,7 +4146,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VSclMax @@ -3928,7 +4157,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VMin @@ -3939,7 +4168,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.VMax @@ -3950,7 +4179,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.Sync @@ -3961,7 +4190,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateChannel @@ -3972,7 +4201,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateOffAut @@ -3983,7 +4212,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateOpAut @@ -3994,7 +4223,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateAutAut @@ -4005,7 +4234,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateOffOp @@ -4016,7 +4245,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateOpOp @@ -4027,7 +4256,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateAutOp @@ -4038,7 +4267,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateOpAct @@ -4049,7 +4278,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateAutAct @@ -4060,7 +4289,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.StateOffAct @@ -4071,7 +4300,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.SrcChannel @@ -4082,7 +4311,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.SrcExtAut @@ -4093,7 +4322,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.SrcIntOp @@ -4104,7 +4333,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.SrcIntAut @@ -4115,7 +4344,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.SrcExtOp @@ -4126,7 +4355,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.SrcIntAct @@ -4137,7 +4366,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_ana.op_src_mode.SrcExtAct @@ -4148,7 +4377,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.tag_name @@ -4159,7 +4388,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.tag_description @@ -4170,9 +4399,9 @@ 1 - + - ns=3;s=services.dummy.configuration_parameters.serv_param_dint.OSLevel + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.WQC 3 @@ -4181,9 +4410,9 @@ 1 - + - ns=3;s=services.dummy.configuration_parameters.serv_param_dint.WQC + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.OSLevel 3 @@ -4192,7 +4421,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VOp @@ -4203,7 +4432,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VInt @@ -4214,7 +4443,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VExt @@ -4225,7 +4454,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VReq @@ -4236,7 +4465,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VOut @@ -4247,7 +4476,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VFbk @@ -4258,7 +4487,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VUnit @@ -4269,7 +4498,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VSclMin @@ -4280,7 +4509,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VSclMax @@ -4291,7 +4520,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VMin @@ -4302,7 +4531,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.VMax @@ -4313,7 +4542,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.Sync @@ -4324,7 +4553,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateChannel @@ -4335,7 +4564,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateOffAut @@ -4346,7 +4575,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateOpAut @@ -4357,7 +4586,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateAutAut @@ -4368,7 +4597,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateOffOp @@ -4379,7 +4608,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateOpOp @@ -4390,7 +4619,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateAutOp @@ -4401,7 +4630,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateOpAct @@ -4412,7 +4641,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateAutAct @@ -4423,7 +4652,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.StateOffAct @@ -4434,7 +4663,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.SrcChannel @@ -4445,7 +4674,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.SrcExtAut @@ -4456,7 +4685,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.SrcIntOp @@ -4467,7 +4696,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.SrcIntAut @@ -4478,7 +4707,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.SrcExtOp @@ -4489,7 +4718,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.SrcIntAct @@ -4500,7 +4729,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_dint.op_src_mode.SrcExtAct @@ -4511,7 +4740,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.tag_name @@ -4522,7 +4751,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.tag_description @@ -4533,9 +4762,9 @@ 1 - + - ns=3;s=services.dummy.configuration_parameters.serv_param_bin.OSLevel + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.WQC 3 @@ -4544,9 +4773,9 @@ 1 - + - ns=3;s=services.dummy.configuration_parameters.serv_param_bin.WQC + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.OSLevel 3 @@ -4555,7 +4784,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VOp @@ -4566,7 +4795,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VInt @@ -4577,7 +4806,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VExt @@ -4588,7 +4817,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VReq @@ -4599,7 +4828,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VOut @@ -4610,7 +4839,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VFbk @@ -4621,7 +4850,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VState0 @@ -4632,7 +4861,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.VState1 @@ -4643,7 +4872,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.Sync @@ -4654,7 +4883,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateChannel @@ -4665,7 +4894,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateOffAut @@ -4676,7 +4905,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateOpAut @@ -4687,7 +4916,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateAutAut @@ -4698,7 +4927,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateOffOp @@ -4709,7 +4938,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateOpOp @@ -4720,7 +4949,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateAutOp @@ -4731,7 +4960,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateOpAct @@ -4742,7 +4971,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateAutAct @@ -4753,7 +4982,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.StateOffAct @@ -4764,7 +4993,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.SrcChannel @@ -4775,7 +5004,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.SrcExtAut @@ -4786,7 +5015,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.SrcIntOp @@ -4797,7 +5026,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.SrcIntAut @@ -4808,7 +5037,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.SrcExtOp @@ -4819,7 +5048,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.SrcIntAct @@ -4830,7 +5059,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_bin.op_src_mode.SrcExtAct @@ -4841,7 +5070,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.tag_name @@ -4852,7 +5081,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.tag_description @@ -4863,9 +5092,9 @@ 1 - + - ns=3;s=services.dummy.configuration_parameters.serv_param_str.OSLevel + ns=3;s=services.dummy.configuration_parameters.serv_param_str.WQC 3 @@ -4874,9 +5103,9 @@ 1 - + - ns=3;s=services.dummy.configuration_parameters.serv_param_str.WQC + ns=3;s=services.dummy.configuration_parameters.serv_param_str.OSLevel 3 @@ -4885,7 +5114,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.VOp @@ -4896,7 +5125,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.VInt @@ -4907,7 +5136,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.VExt @@ -4918,7 +5147,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.VReq @@ -4929,7 +5158,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.VOut @@ -4940,7 +5169,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.VFbk @@ -4951,7 +5180,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.Sync @@ -4962,7 +5191,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateChannel @@ -4973,7 +5202,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateOffAut @@ -4984,7 +5213,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateOpAut @@ -4995,7 +5224,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateAutAut @@ -5006,7 +5235,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateOffOp @@ -5017,7 +5246,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateOpOp @@ -5028,7 +5257,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateAutOp @@ -5039,7 +5268,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateOpAct @@ -5050,7 +5279,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateAutAct @@ -5061,7 +5290,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.StateOffAct @@ -5072,7 +5301,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.SrcChannel @@ -5083,7 +5312,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.SrcExtAut @@ -5094,7 +5323,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.SrcIntOp @@ -5105,7 +5334,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.SrcIntAut @@ -5116,7 +5345,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.SrcExtOp @@ -5127,7 +5356,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.SrcIntAct @@ -5138,7 +5367,7 @@ 1 - + ns=3;s=services.dummy.configuration_parameters.serv_param_str.op_src_mode.SrcExtAct @@ -5149,7 +5378,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_1.tag_name @@ -5160,7 +5389,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_1.tag_description @@ -5171,7 +5400,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_1.WQC @@ -5182,7 +5411,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_1.IsSelfCompleting @@ -5193,7 +5422,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_1.ProcedureId @@ -5204,7 +5433,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_1.IsDefault @@ -5215,7 +5444,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_2.tag_name @@ -5226,7 +5455,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_2.tag_description @@ -5237,7 +5466,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_2.WQC @@ -5248,7 +5477,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_2.IsSelfCompleting @@ -5259,7 +5488,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_2.ProcedureId @@ -5270,7 +5499,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_2.IsDefault @@ -5281,7 +5510,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.tag_name @@ -5292,7 +5521,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.tag_description @@ -5303,7 +5532,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.WQC @@ -5314,7 +5543,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.IsSelfCompleting @@ -5325,7 +5554,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.ProcedureId @@ -5336,7 +5565,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.IsDefault @@ -5347,7 +5576,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.tag_name @@ -5358,7 +5587,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.tag_description @@ -5369,9 +5598,9 @@ 1 - + - ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.OSLevel + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.WQC 3 @@ -5380,9 +5609,9 @@ 1 - + - ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.WQC + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.OSLevel 3 @@ -5391,7 +5620,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VOp @@ -5402,7 +5631,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VInt @@ -5413,7 +5642,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VExt @@ -5424,7 +5653,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VReq @@ -5435,7 +5664,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VOut @@ -5446,7 +5675,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VFbk @@ -5457,7 +5686,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VUnit @@ -5468,7 +5697,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VSclMin @@ -5479,7 +5708,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VSclMax @@ -5490,7 +5719,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VMin @@ -5501,7 +5730,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.VMax @@ -5512,7 +5741,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.Sync @@ -5523,7 +5752,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateChannel @@ -5534,7 +5763,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateOffAut @@ -5545,7 +5774,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateOpAut @@ -5556,7 +5785,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateAutAut @@ -5567,7 +5796,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateOffOp @@ -5578,7 +5807,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateOpOp @@ -5589,7 +5818,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateAutOp @@ -5600,7 +5829,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateOpAct @@ -5611,7 +5840,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateAutAct @@ -5622,7 +5851,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.StateOffAct @@ -5633,7 +5862,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.SrcChannel @@ -5644,7 +5873,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.SrcExtAut @@ -5655,7 +5884,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.SrcIntOp @@ -5666,7 +5895,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.SrcIntAut @@ -5677,7 +5906,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.SrcExtOp @@ -5688,7 +5917,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.SrcIntAct @@ -5699,7 +5928,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_ana.op_src_mode.SrcExtAct @@ -5710,7 +5939,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.tag_name @@ -5721,7 +5950,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.tag_description @@ -5732,9 +5961,9 @@ 1 - + - ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.OSLevel + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.WQC 3 @@ -5743,9 +5972,9 @@ 1 - + - ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.WQC + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.OSLevel 3 @@ -5754,7 +5983,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VOp @@ -5765,7 +5994,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VInt @@ -5776,7 +6005,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VExt @@ -5787,7 +6016,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VReq @@ -5798,7 +6027,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VOut @@ -5809,7 +6038,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VFbk @@ -5820,7 +6049,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VUnit @@ -5831,7 +6060,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VSclMin @@ -5842,7 +6071,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VSclMax @@ -5853,7 +6082,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VMin @@ -5864,7 +6093,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.VMax @@ -5875,7 +6104,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.Sync @@ -5886,7 +6115,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateChannel @@ -5897,7 +6126,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateOffAut @@ -5908,7 +6137,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateOpAut @@ -5919,7 +6148,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateAutAut @@ -5930,7 +6159,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateOffOp @@ -5941,7 +6170,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateOpOp @@ -5952,7 +6181,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateAutOp @@ -5963,7 +6192,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateOpAct @@ -5974,7 +6203,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateAutAct @@ -5985,7 +6214,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.StateOffAct @@ -5996,7 +6225,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.SrcChannel @@ -6007,7 +6236,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.SrcExtAut @@ -6018,7 +6247,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.SrcIntOp @@ -6029,7 +6258,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.SrcIntAut @@ -6040,7 +6269,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.SrcExtOp @@ -6051,7 +6280,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.SrcIntAct @@ -6062,7 +6291,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_dint.op_src_mode.SrcExtAct @@ -6073,7 +6302,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.tag_name @@ -6084,7 +6313,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.tag_description @@ -6095,9 +6324,9 @@ 1 - + - ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.OSLevel + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.WQC 3 @@ -6106,9 +6335,9 @@ 1 - + - ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.WQC + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.OSLevel 3 @@ -6117,7 +6346,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.VOp @@ -6128,7 +6357,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.VInt @@ -6139,7 +6368,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.VExt @@ -6150,7 +6379,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.VReq @@ -6161,7 +6390,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.VOut @@ -6172,7 +6401,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.VFbk @@ -6183,7 +6412,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.VState0 @@ -6194,7 +6423,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.VState1 @@ -6205,7 +6434,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.Sync @@ -6216,7 +6445,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateChannel @@ -6227,7 +6456,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateOffAut @@ -6238,7 +6467,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateOpAut @@ -6249,7 +6478,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateAutAut @@ -6260,7 +6489,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateOffOp @@ -6271,7 +6500,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateOpOp @@ -6282,7 +6511,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateAutOp @@ -6293,7 +6522,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateOpAct @@ -6304,7 +6533,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateAutAct @@ -6315,7 +6544,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.StateOffAct @@ -6326,7 +6555,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.SrcChannel @@ -6337,7 +6566,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.SrcExtAut @@ -6348,7 +6577,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.SrcIntOp @@ -6359,7 +6588,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.SrcIntAut @@ -6370,7 +6599,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.SrcExtOp @@ -6381,7 +6610,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.SrcIntAct @@ -6392,7 +6621,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_bin.op_src_mode.SrcExtAct @@ -6403,7 +6632,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.tag_name @@ -6414,7 +6643,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.tag_description @@ -6425,9 +6654,9 @@ 1 - + - ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.OSLevel + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.WQC 3 @@ -6436,9 +6665,9 @@ 1 - + - ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.WQC + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.OSLevel 3 @@ -6447,7 +6676,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.VOp @@ -6458,7 +6687,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.VInt @@ -6469,7 +6698,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.VExt @@ -6480,7 +6709,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.VReq @@ -6491,7 +6720,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.VOut @@ -6502,7 +6731,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.VFbk @@ -6513,7 +6742,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.Sync @@ -6524,7 +6753,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateChannel @@ -6535,7 +6764,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateOffAut @@ -6546,7 +6775,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateOpAut @@ -6557,7 +6786,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateAutAut @@ -6568,7 +6797,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateOffOp @@ -6579,7 +6808,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateOpOp @@ -6590,7 +6819,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateAutOp @@ -6601,7 +6830,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateOpAct @@ -6612,7 +6841,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateAutAct @@ -6623,7 +6852,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.StateOffAct @@ -6634,7 +6863,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.SrcChannel @@ -6645,7 +6874,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.SrcExtAut @@ -6656,7 +6885,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.SrcIntOp @@ -6667,7 +6896,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.SrcIntAut @@ -6678,7 +6907,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.SrcExtOp @@ -6689,7 +6918,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.SrcIntAct @@ -6700,7 +6929,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.procedure_parameters.proc_param_str.op_src_mode.SrcExtAct @@ -6711,7 +6940,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_ana.tag_name @@ -6722,7 +6951,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_ana.tag_description @@ -6733,7 +6962,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_ana.OSLevel @@ -6744,7 +6973,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_ana.WQC @@ -6755,7 +6984,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_ana.V @@ -6766,7 +6995,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_ana.VUnit @@ -6777,7 +7006,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_ana.VSclMin @@ -6788,7 +7017,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_ana.VSclMax @@ -6799,7 +7028,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_dint.tag_name @@ -6810,7 +7039,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_dint.tag_description @@ -6821,7 +7050,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_dint.OSLevel @@ -6832,7 +7061,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_dint.WQC @@ -6843,7 +7072,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_dint.V @@ -6854,7 +7083,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_dint.VUnit @@ -6865,7 +7094,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_dint.VSclMin @@ -6876,7 +7105,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_dint.VSclMax @@ -6887,7 +7116,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_bin.tag_name @@ -6898,7 +7127,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_bin.tag_description @@ -6909,7 +7138,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_bin.OSLevel @@ -6920,7 +7149,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_bin.WQC @@ -6931,7 +7160,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_bin.V @@ -6942,7 +7171,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_bin.VState0 @@ -6953,7 +7182,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_bin.VState1 @@ -6964,7 +7193,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_str.tag_name @@ -6975,7 +7204,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_str.tag_description @@ -6986,7 +7215,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_str.OSLevel @@ -6997,7 +7226,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_str.WQC @@ -7008,7 +7237,7 @@ 1 - + ns=3;s=services.dummy.procedures.proc_3.report_values.proc_rv_str.V @@ -7019,141 +7248,9 @@ 1 - - - ns=3;s=services.dummy.op_src_mode.StateChannel - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.StateOffAut - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.StateOpAut - - - 3 - - - 1 - - - + - ns=3;s=services.dummy.op_src_mode.StateAutAut - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.StateOffOp - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.StateOpOp - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.StateAutOp - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.StateOpAct - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.StateAutAct - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.StateOffAct - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.SrcChannel - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.SrcExtAut - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.op_src_mode.SrcIntOp + ns=3;s=services.dummy.procedure_control.ProcedureOp 3 @@ -7162,9 +7259,9 @@ 1 - + - ns=3;s=services.dummy.op_src_mode.SrcIntAut + ns=3;s=services.dummy.procedure_control.ProcedureInt 3 @@ -7173,9 +7270,9 @@ 1 - + - ns=3;s=services.dummy.op_src_mode.SrcExtOp + ns=3;s=services.dummy.procedure_control.ProcedureExt 3 @@ -7184,9 +7281,9 @@ 1 - + - ns=3;s=services.dummy.op_src_mode.SrcIntAct + ns=3;s=services.dummy.procedure_control.ProcedureCur 3 @@ -7195,9 +7292,9 @@ 1 - + - ns=3;s=services.dummy.op_src_mode.SrcExtAct + ns=3;s=services.dummy.procedure_control.ProcedureReq 3 @@ -7206,7 +7303,7 @@ 1 - + ns=3;s=services.dummy.state_machine.CommandOp @@ -7217,7 +7314,7 @@ 1 - + ns=3;s=services.dummy.state_machine.CommandInt @@ -7228,7 +7325,7 @@ 1 - + ns=3;s=services.dummy.state_machine.CommandExt @@ -7239,7 +7336,7 @@ 1 - + ns=3;s=services.dummy.state_machine.StateCur @@ -7250,7 +7347,7 @@ 1 - + ns=3;s=services.dummy.state_machine.CommandEn @@ -7261,71 +7358,16 @@ 1 - - - ns=3;s=services.dummy.procedure_control.ProcedureOp - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.procedure_control.ProcedureInt - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.procedure_control.ProcedureExt - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.procedure_control.ProcedureCur - - - 3 - - - 1 - - - - - ns=3;s=services.dummy.procedure_control.ProcedureReq - - - 3 - - - 1 - - - - + + - vP0R-v418-SydE-YGMa-9l8n + InB6-mpWt-9yJQ-Yn2y-VqSW @@ -7333,38 +7375,38 @@ - - + + - Y9nh-ZYOj-D13L-f3Lj-kD7B + LmVk-ox94-8Nfo-fZrd-NyPq - + - Gq27-6Jon-hJdl-fk50-laR8 + GdrK-5EV1-WNOu-h9Ee-YZb2 - + - OWoA-Ipu3-LJKp-F9Ee-EJh3 + 3hLT-t2Ub-ebuy-7szi-zRQ9 - + - jAme-IlwP-HK0v-TEQU-0QHD + 40jA-EyKs-T4e3-fh2n-wdNX - + - ODP8-T3cW-ENLa-p2zC-HqMR + LBGP-5jWD-Fmus-qnTf-inJP - + - 0 + 1 False @@ -7373,13 +7415,13 @@ False - GRDh-omR5-xMme-LlJA-dDx0 + afbI-LaKY-PvmG-B31c-kx5f - + - 1 + 2 True @@ -7388,13 +7430,13 @@ True - VELF-Bx0c-xdL3-NgXS-Zm5T + EF0Y-eXga-TsEn-lV6w-auQB - + - 2 + 3 False @@ -7403,53 +7445,53 @@ True - m1IK-dmiM-l91o-VD2j-JWqQ + 5Xic-XpxI-c8wZ-f8kl-axMW - + - AgfH-Mi2s-eFEB-HrZM-2ETD + pjZa-AURp-IfsS-Enh7-OlSy - + - hADx-uTnV-DCjY-PpWj-JqmR + 6dzn-zCxM-orJC-g0zh-36rl - + - lg0V-kOfW-Wrmg-8xBV-xr6O + A83w-Kpvs-lpwj-EKh0-8MmX - + - waWs-Acno-DAz5-cJXs-x8AP + KwHW-OJDm-Dkzy-9eW5-nURv - + - kasC-KHPD-7qns-FBky-Epje + Wu4i-9Z6g-Bfcb-bypW-1QrJ - + - xtDa-fBDy-q2tX-bJE8-3pVL + 3SJ8-HXgp-VLnX-dLVW-zx9E - + - Purl-2bCJ-Vw1J-YhfX-Cnlw + SDOb-bGH8-EYCg-Y4My-oM9r - + - QsWD-eUKw-j2a6-0p4m-SkU7 + yLb7-mbNB-C6vk-bsRI-B9Cx diff --git a/readme.md b/readme.md index 458e3df..0987970 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,7 @@ # MTPPy ## Modular automation + Modular Automation defined in VDI/VDE/NAMUR 2658 offers promising technologies for the process and chemical industry. It meets the requirements for greater flexibility and a shorter development cycle for production facilities by dividing the process into smaller standardized units. Key element of Modular Automation is the Module Type Package (MTP). @@ -8,20 +9,21 @@ It represents a standardized and manufacturer-independent description of the aut (PEA), and endows the plug-and-produce capability of PEAs. ## Description + The structure of the package mirrors the service interface defined in VDI/VDE/NAMUR 2658 Parts 1, 3, and 4. Each data assembly has its own class inherited parameters and methods from a higher level class. The design of the architecture is based on an assumption, that addition of new data assemblies must be possible. The top level of the application is defined by the class OPCUAServerPEA. This class represents the OPC UA server instance for a single PEA and is responsible for the event-based connection between data assembly’s attributes and the OPC UA server. -This class contains a service set in the form of a dictionary of implemented services as well as a list of active elements defined on the PEA level. +This class contains a service set in the form of a dictionary of implemented services as well as a list of indicator, operation and active elements defined on the PEA level. The class OPCUAServerPEA can also generate the MTP manifest. For this it recursively iterates through the structure of the user-defined service set and adds corresponding entities to the overall MTP structure. In the end, the method outputs the MTP manifest as an aml file, to be imported into a Process Orchestration Layer software. -Due to open-source paradigm, the MTP manifest can also be extended by other components, e.g. safety relations or energy consumption aspects that currently not defined in the MTP standard.\newline +Due to open-source paradigm, the MTP manifest can also be extended by other components, e.g. safety relations or energy consumption aspects that currently not defined in the MTP standard. The class Service represents single services according to VDI/VDE/NAMUR 2658 and provides methods to add configuration parameters and procedures to the service. Single states of the state machine, e.g. idle, starting, execute, completing, etc., are defined as abstract methods that must be further defined by the user for each concrete service. The class Service also contains further objects that are responsible for Operation and Source Modes, State Machine and Procedures. -Each of them is implemented as a corresponding class where internal logic according to the standard is defined internally, e.g. transition from Offline mode to Operator mode, CommandEn, state change for states of the state machine.\newline +Each of them is implemented as a corresponding class where internal logic according to the standard is defined internally, e.g. transition from Offline mode to Operator mode, CommandEn, state change for states of the state machine. Procedures as instances of the class Procedure can be added to a service using the corresponding method in the class Service. The procedure itself is defined by means of related procedure parameters, process value in, process value out, and report values. The class ProcedureControl serves as a manager for procedures and controls the execution of the selected procedures on the service level. @@ -30,156 +32,136 @@ CommandEnControl is another class defined on the service level as an object of t It represents an object to control state changes that can be executed by the state machine. Each time any command is received from the OPC UA server, a check is made whether the incoming command can be executed from the current state as defined in the VDI/VDE/NAMUR 2658. - Currently only selected data assemblies that are necessarily relevant for soft sensors are implemented. -This includes IndicatorElements (BinView, AnaView, DIntView, and StringView), OperationElements (BinServParam, AnaServParam, DIntServParamt, StringServParam) and ActiveElements (AnaVlv, BinVlv, AnaDrv, BinDrv, PIDCtrl), which are defined in Part 4 of VDI/VDE/NAMUR 2658. +This includes IndicatorElements (BinView, AnaView, DIntView, and StringView), OperationElements (BinMan, AnaMan, DIntMan, BinManInt, AnaManInt, DIntManInt) and ActiveElements (AnaVlv, BinVlv, AnaDrv, BinDrv, PIDCtrl), which are defined in Part 4 of VDI/VDE/NAMUR 2658. Nevertheless, the architecture of the package allows the development of further data assembly classes. - The single states run in separate threads that are managed in the class ThreadControl. This class makes sure that after each state change the thread that corresponds to the previous state will be naturally terminated, and another thread of the next state started. -Having separate threads allows the running of additional operations, such as data pre-processing or model inference parallel to the main functions of the PEA.\newline +Having separate threads allows the running of additional operations, such as data pre-processing or model inference parallel to the main functions of the PEA. Coupling of data assemblies with OPC UA variables is done by means of event-based subscriptions. Each OPC UA node that allows changes from the operator or POL via the OPC UA interface creates a subscription object with the corresponding callback method. Each time a change happens, it triggers the callback method with its new value as an input argument. For example, an incoming command to start the service will run checks first and then execute the corresponding service method named start.\newline -The architecture of MTPPy is depicted in Figure \ref{fig:class_diagram} in the form of a simplified class diagram. +The architecture of MTPPy is depicted in Figure in the res folder in the form of a simplified class diagram. ## Installation + To install the package via pip: + ```console pip3 install mtppy ``` ## Development workflow + To develop a PEA using MTPPy, the following workflow is advised. First, an object of the class OPCUAServerPEA is initialized. The server endpoint and port can be defined in the attribute named endpoint. Each service must be defined accordingly: + * Configuration parameters are defined and attached to the service. * At least one procedure must be defined within the service. The procedure might also have internal parameters that need to be also defined and added to the procedure. * The central task of the developer is to detail the business logic of each state in the service. -For this, abstract methods of the instantiated Service class objects must be defined. -Here, service configuration parameters, and procedure parameters can be easily accessed and manipulated. -For example, in the state starting the required ML model can be imported. -Further, in the state execute, data are requested, pre-processed, and fed to the prediction function. -Further, the predicted value is put into a specific report value. -It is then available for other PEAs or within the process control system. -Here, the main advantage of MTPPy is clearly visible. -The developer can seamlessly implement data analytical functionality into services and other types of data assemblies directly in the Python environment without building interfaces to separate components. + For this, abstract methods of the instantiated Service class objects must be defined. + Here, service configuration parameters, and procedure parameters can be easily accessed and manipulated. + For example, in the state starting the required ML model can be imported. + Further, in the state execute, data are requested, pre-processed, and fed to the prediction function. + Further, the predicted value is put into a specific report value. + It is then available for other PEAs or within the process control system. + Here, the main advantage of MTPPy is clearly visible. + The developer can seamlessly implement data analytical functionality into services and other types of data assemblies directly in the Python environment without building interfaces to separate components. * Service must be added to the OPCUAServerPEA instance. -* Active elements defined on the PEA level must be also added to the OPCUAServerPEA instance. +* Elements defined on the PEA level must be also added to the OPCUAServerPEA instance. After definition of the service set, the MTP file can be generated and the instance of OPCUAServerPEA can be started in one code line. ## Example +More examples can be found the the example folder. Those show: + +* minimal virtual PEA +* virtual PEA with recipe +* virtual PEA with pause hold and restart +* virtual PEA with events +* Data Objects + ### Service definition + ```python from mtppy.service import Service from mtppy.procedure import Procedure -from mtppy.operation_elements import * +from mtppy.parameter_elements import * from mtppy.indicator_elements import * +_log = logging.getLogger(__name__) + + class RandomNumberGenerator(Service): def __init__(self, tag_name, tag_description): super().__init__(tag_name, tag_description) # Procedure definition - proc_0 = Procedure(0, 'cont', is_self_completing=False, is_default=True) + proc_1 = Procedure(1, 'cont', is_self_completing=False, is_default=True) # Adding two procedure parameters - proc_0.add_procedure_parameter( + proc_1.add_procedure_parameter( DIntServParam('lower_bound', v_min=0, v_max=100, v_scl_min=0, v_scl_max=100, v_unit=23)) - proc_0.add_procedure_parameter( + proc_1.add_procedure_parameter( DIntServParam('upper_bound', v_min=0, v_max=100, v_scl_min=0, v_scl_max=100, v_unit=23)) + # optional: link op_src_mode of procedure parameters to service op_src_mode + for parameter in proc_1.procedure_parameters.values(): + self.op_src_mode.add_linked_op_src_mode(parameter.op_src_mode) + # Adding procedure report value - proc_0.add_report_value( + proc_1.add_report_value( AnaView('generated_value', v_scl_min=0, v_scl_max=100, v_unit=23), ) # Allocating procedure to the service - self.add_procedure(proc_0) - - def idle(self): - print('- Idle -') - cycle = 0 - while self.is_state('idle'): - print(f'Idle cycle {cycle}') - print('Doing nothing...') - cycle += 1 - time.sleep(1) + self.add_procedure(proc_1) def starting(self): - print('- Starting -') - print('Applying procedure parameters...') - self.state_change() + _log.info('- Starting -') + _log.info('Applying procedure parameters...') def execute(self): - print('- Execute -') + _log.info('- Execute -') cycle = 0 while self.is_state('execute'): - print('Execute cycle %i' % cycle) + _log.info('Execute cycle %i' % cycle) # Read procedure parameters - lower_bound = self.procedures[0].procedure_parameters['lower_bound'].get_v_out() - upper_bound = self.procedures[0].procedure_parameters['upper_bound'].get_v_out() + lower_bound = self.procedures[1].procedure_parameters['lower_bound'].get_v_out() + upper_bound = self.procedures[1].procedure_parameters['upper_bound'].get_v_out() # Execute random number generation generated_number = random.randint(lower_bound, upper_bound) # Return report value - self.procedures[0].report_values['generated_value'].set_v(generated_number) + self.procedures[1].report_values['generated_value'].set_v(generated_number) cycle += 1 time.sleep(0.1) def completing(self): - self.state_change() - - def completed(self): - pass + _log.info('- Completing -') - def pausing(self): - pass - - def paused(self): - pass - - def resuming(self): - pass - - def holding(self): - pass - - def held(self): - pass - - def unholding(self): - pass - - def stopping(self): - pass - - def stopped(self): - pass + # starting, execute, completing methods have to be defined for each service + # other state methods (see service documentation) can be overridden as needed, + # by default they only log state entries +``` - def aborting(self): - pass +Take into account that the **starting**, **execute** and **completing** states need to be explicitly defined, even if there are no actions. The other states log their entries by default and can be overridden to implement functionality. - def aborted(self): - pass +States must be implemented in a way that they can react to state changes by checking the `is_state("name_of_state")` function regularly. An event-based system is also possible. For more information, see the corresponding example. - def resetting(self): - print('- Resetting -') - self.state_change() -``` - -Take into account that all state need to be explicitly defined. Even if there are no actions. -In additional, active elemements that are service-independent should be defined and added to the PEA. +In addition, elements that are service-independent should be defined and added to the PEA. ### PEA definition + Now, the service with its procedure can be added to the PEA instance. + ```python from mtppy.opcua_server_pea import OPCUAServerPEA from mtppy.active_elements import PIDCtrl @@ -198,10 +180,13 @@ module.add_active_element(pid_ctrl) print('--- Start OPC UA server ---') module.run_opcua_server() ``` + The last line will start the OPC UA server. ### MTP generation + To generate an MTP manifest, instantiate an MTP generator. + ```python from mtppy.mtp_generator import MTPGenerator @@ -220,6 +205,7 @@ The manifest template can be downloaded here: https://github.com/p2o-lab/MTPPy/blob/master/manifest_files/manifest_template.xml and add it as an input argument to the PEA instance. + ```python module = OPCUAServerPEA(mtp_generator) -``` \ No newline at end of file +``` diff --git a/requirements.txt b/requirements.txt index 77eeb4a..defcd16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -opcua~=0.98.13 \ No newline at end of file +opcua~=0.98.13 +simple_pid~=2.0.1 \ No newline at end of file diff --git a/res/architecture/class_diagram.drawio b/res/architecture/class_diagram.drawio new file mode 100644 index 0000000..e7fd9a6 --- /dev/null +++ b/res/architecture/class_diagram.drawio @@ -0,0 +1,2835 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/architecture/class_entitys.txt b/res/architecture/class_entitys.txt new file mode 100644 index 0000000..619cd4e --- /dev/null +++ b/res/architecture/class_entitys.txt @@ -0,0 +1,737 @@ +Locks ++perm_en: bool ++intl_en: bool ++prot_en: bool ++attributes: dict +-- ++permit_status(): bool ++interlock_status(): bool ++protect_status(): bool +-set_permit(bool): None +-set_interlock(bool): None +-set_protect(bool, reset=False): None +-_run_allowed(): bool + +AnaVlv ++op_src_mode: OperationSourceModeElement ++locks: Locks ++pos_min: float ++pos_max: float ++pos_scl_min: float ++pos_scl_max: float ++pos_unit: int ++open_fbk_calc: bool ++close_fbk_calc: bool ++pos_fbk_calc: bool ++safe_pos: int ++safe_pos_en: bool +-- ++get_pos(): float ++get_pos_rbk(): float ++get_pos_fbk(): float ++get_open_fbk(): bool ++get_close_fbk(): bool +-set_open_aut(bool): None +-set_close_aut(bool): None +-set_reset_aut(bool): None +-set_open_op(bool): None +-set_close_op(bool): None +-set_pos_int(float): None +-set_pos_man(float): None +-set_pos_rbk(float): None +-set_pos_fbk(float): None +-set_open_fbk(bool): None +-set_close_fbk(bool): None +-_expect_save_pos(): None +-_go_save_pos(): None +-_run_allowed(): bool +-_set_pos(float): None + +MonAnaVlv ++mon_en: bool ++mon_safe_pos: bool ++mon_stat_ti: float ++mon_dyn_ti: float ++pos_tolerance: float +-- ++start_monitor(): None ++set_stop_monitor(): None +-monitor_static_error(): None +-monitor_dynamic_error(): None +-monitor_position_reached(): None +-_handle_monitored_error(): None + +BinVlv ++op_src_mode: OperationMode ++locks: Locks ++open_fbk_calc: bool ++close_fbk_calc: bool ++safe_pos: int ++safe_pos_en: bool +-- ++get_open_fbk(): bool ++get_close_fbk(): bool ++get_ctrl(): bool +-set_open_aut(bool): None +-set_close_aut(bool): None +-set_reset_aut(bool): None +-set_open_op(bool): None +-set_close_op(bool): None +-set_ctrl(bool): None +-_expect_save_pos(): None +-_go_safe_pos(): None +-_run_allowed(bool): bool + +MonBinVlv ++mon_en: bool ++mon_safe_pos: bool ++mon_stat_ti: float ++mon_dyn_ti: float +-- ++start_monitor(): None ++set_stop_monitor(): None +-monitor_static_error(): None +-monitor_dynamic_error(): None +-_handle_monitored_error(): None + +BinDrv ++op_src_mode: OperationMode ++locks: Locks ++rev_fbk_calc: bool ++fwd_fbk_calc: bool ++safe_pos: int ++fwd_en: bool ++rev_en: bool +-- ++get_fwd_fbk(): bool ++get_rev_fbk(): bool +-set_fwd_op(bool): None +-set_rev_op(bool): None +-set_stop_op(bool): None +-set_fwd_aut(bool): None +-set_rev_aut(bool): None +-set_stop_aut(bool): None +-_expect_save_pos(): None +-_go_safe_pos(): None +-_run_allowed(): bool + +MonBinDrv ++mon_en: bool ++mon_safe_pos: bool ++mon_stat_ti: float ++mon_dyn_ti: float +-- ++start_monitor(): None ++set_stop_monitor(): None +-monitor_static_error(): None +-monitor_dynamic_error(): None +-_handle_monitored_error(): None + +AnaDrv ++op_src_mode: OperationSourceModeElement ++locks: Locks ++rpm_min: float ++rpm_max: float ++rpm_scl_min: float ++rpm_scl_max: float ++rpm_unit: int ++rev_fbk_calc: bool ++fwd_fbk_calc: bool ++rpm_fbk_calc: bool ++safe_pos: int ++fwd_en: bool ++rev_en: bool +-- ++get_rpm(): float ++get_rpm_rbk(): float ++get_rpm_fbk(): float +-set_fwd_op(bool): None +-set_rev_op(bool): None +-set_stop_op(bool): None +-set_fwd_aut(bool): None +-set_rev_aut(bool): None +-set_stop_aut(bool): None +-set_rpm_int(float): None +-set_rpm_man(float): None +-set_rpm_rbk(float): None +-set_rpm_fbk(float): None +-set_trip(bool): None +-_expect_save_pos(): None +-_go_save_pos(): None +-_run_allowed(): bool + +MonAnaDrv ++mon_en: bool ++mon_safe_pos: bool ++mon_stat_ti: float ++mon_dyn_ti: float ++rpm_ah_en: bool ++rpm_al_en: bool ++rpm_ah_lim: float ++rpm_al_lim: float +-- ++start_monitor(): None ++set_stop_monitor(): None +-monitor_static_error(): None +-monitor_dynamic_error(): None +-monitor_rpm_error(): None +-monitor_rpm_high_limit(): None +-monitor_rpm_low_limit(): None +-_handle_monitored_error(): None + +PIDController ++kp: float ++ki: float ++kd: float ++mv_min: float ++mv_max: float ++sp: float ++pv: float ++mv: float ++sample_time: float +-- ++start(): None ++stop(): None ++set_sp(float): None ++set_pv(float): None ++get_mv(): float ++set_kp(float): None ++set_ki(float): None ++set_kd(float): None + +PIDCtrl ++op_src_mode: OperationSourceModeElement ++pv_scl_min: float ++pv_scl_max: float ++pv_unit: int ++sp_scl_min: float ++sp_scl_max: float ++sp_unit: int ++sp_int_min: float ++sp_int_max: float ++sp_man_min: float ++sp_man_max: float ++mv_min: float ++mv_max: float ++mv_unit: int ++mv_scl_min: float ++mv_scl_max: float ++P: float ++Ti: float ++Td: float +-- ++get_mv(): float ++get_sp(): float ++set_pv(float): None ++set_sp_man(float): None ++set_sp_int(float): None ++set_mv_man(float): None +-set_p(float): None +-set_ti(float): None +-set_td(float): None +-_start_aut_mv_update_loop(): None +-_stop_aut_mv_update_loop(): None + +Attribute ++name: str ++type: type ++init_value: Any ++value: Any ++comm_obj: Any ++sub_cb: Callable[[Any], None] +-- ++set_value(Any): bool ++attach_communication_object(Any): None ++attach_subscription_callback(Callable[[Any], None]): None ++remove_subscription_callback(): None +-_correct_type(Any): Any + +CommandCodes ++reset: int ++start: int ++stop: int ++hold: int ++unhold: int ++pause: int ++resume: int ++abort: int ++restart: int ++complete: int ++int_code: dict +-- ++get_list_int(): list ++get_list_str(): list + +CommandEnControl ++command_en: dict ++hold_enabled: bool ++pause_enabled: bool ++restart_enabled: bool +-- ++set_default(): None ++disable_all(): None ++is_enabled(str): bool ++get_command_en(): int ++set_command_en(str, bool): None ++enable_hold_loop(bool): None ++enable_pause_loop(bool): None ++enable_restart(bool): None ++disable_restart_temporarily(): None ++restore_restart(): None ++execute(str): None + +OPCUACommunicationObject ++opcua_node_obj: object ++node_id: str ++write_value_callback: Callable +-- + +AnaView ++v_scl_min: float ++v_scl_max: float ++v_unit: int +-- ++set_v(float): None + +BinView ++v_state_0: str ++v_state_1: str +-- ++set_v(bool): None + +DIntView ++v_scl_min: int ++v_scl_max: int ++v_unit: int +-- ++set_v(int): None + +StringView +-- ++set_v(str): None + +MTPGenerator ++template_path: str ++writer_infos: dict ++tree: ET.ElementTree ++root: ET.Element ++export_path: str ++instance_hierarchy_mtp: ET.Element ++instance_hierarchy_service: ET.Element ++module_type_package: ET.Element ++communication_set: ET.Element ++services_set: ET.Element ++instance_list: ET.Element ++source_list: ET.Element ++opcua_server: ET.Element ++service: ET.Element ++service_procedure: ET.Element +-- ++edit_writer_information(): None ++add_module_type_package(str, str, str): None ++create_instance(SUCDataAssembly, str): ET.Element ++add_data_assembly_to_instance_list(str, str, str, str): ET.Element ++create_components_for_services(SUCDataAssembly, str): str ++add_components_to_services(SUCDataAssembly, str, str): str ++add_opcua_server(str): None ++add_external_interface(str, int, str, int): None ++apply_add_supported_role_class(): None ++export_manifest(): None ++pretty_print(ET.Element, int): None +-_init_manifest(): None +-_init_instance_hierarchies(): None +-_add_communication_set(): None +-_add_services_set(str): None +-_add_service_to_service_set(str, str, str): None +-_add_linked_attr(ET.Element, str): None +-_generate_attributes(ET.Element, str, str, str): None +-_random_id_generator(): str + +OPCUAServerPEA ++service_set: dict[str, Service] ++active_elements: dict[str, SUCActiveElement] ++indicator_elements: dict[str, SUCIndicatorElement] ++operation_elements: dict[str, SUCOperationElement] ++custom_data_assembly_sets: dict[str, dict[str, SUCDataAssembly]] ++endpoint: str ++opcua_server: Server ++opcua_ns: int ++subscription_list: SubscriptionList ++mtp: MTPGenerator ++_folders: list[str] ++_leaves: list[str] +-- ++add_service(Service): None ++add_active_element(SUCActiveElement): None ++add_indicator_element(SUCIndicatorElement): None ++add_operation_element(SUCOperationElement): None ++add_custom_data_assembly_set(str, dict[str, SUCDataAssembly]): None ++add_folders(list[str]): None ++add_leaves(list[str]): None ++get_opcua_server(): Server ++get_opcua_ns(): int ++run_opcua_server(): None ++set_services_in_idle(): None +-_init_opcua_server(): None +-_build_opcua_server(): None +-_create_opcua_element(dict[str, SUCDataAssembly], str): None +-_create_opcua_objects_for_folders(SUCDataAssembly, str, Node, str): None +-_create_opcua_objects_for_leaves(SUCDataAssembly, str, Node, ET.Element): None +-_start_subscription(): None +-_infer_data_type(type): ua.VariantType +-_get_objects_attributes(object): dict + +SubscriptionList ++sub_list: dict +-- ++append(Node, Callable): None ++get_nodeid_list(): list ++get_callback(Node): Callable + +Marshalling ++subscription_list: SubscriptionList +-- ++import_subscription_list(SubscriptionList): None ++datachange_notification(Node, Any, Any): None ++find_set_callback(Node): Callable + +AnaMan ++v_min: float ++v_max: float ++v_scl_min: float ++v_scl_max: float ++v_unit: int +-- ++valid_value(float): bool ++set_v_out(float): None ++set_v_rbk(float): None ++set_v_fbk(float): None ++get_v_out(): float + +AnaManInt ++op_src_mode: SourceMode +-- ++set_v_int(float): None + +DIntMan ++v_min: int ++v_max: int ++v_scl_min: int ++v_scl_max: int ++v_unit: int +-- ++valid_value(int): bool ++set_v_out(int): None ++set_v_rbk(int): None ++set_v_fbk(int): None ++get_v_out(): int + +DIntManInt ++op_src_mode: SourceMode +-- ++set_v_int(int): None + +BinMan ++v_state_0: str ++v_state_1: str +-- ++set_v_out(bool): None ++set_v_rbk(bool): None ++set_v_fbk(bool): None ++get_v_out(): bool + +BinManInt ++op_src_mode: SourceMode +-- ++set_v_int(bool): None + +OperationSourceMode ++attributes: dict[str, Attribute] ++switch_to_offline_mode_allowed: bool ++enter_offline_callbacks: list[callable] ++exit_offline_callbacks: list[callable] ++enter_operator_callbacks: list[callable] ++exit_operator_callbacks: list[callable] ++enter_automatic_callbacks: list[callable] ++exit_automatic_callbacks: list[callable] ++linked_op_src_modes: list[OperationSourceMode] +-- ++allow_switch_to_offline_mode(bool): None ++add_enter_offline_callback(callable): None ++add_exit_offline_callback(callable): None ++add_enter_operator_callback(callable): None ++add_exit_operator_callback(callable): None ++add_enter_automatic_callback(callable): None ++add_exit_automatic_callback(callable): None ++add_linked_op_src_mode(OperationSourceMode): None ++set_state_channel(bool): None ++set_state_aut_aut(bool): None ++set_state_aut_op(bool): None ++set_state_off_aut(bool): None ++set_state_off_op(bool): None ++set_state_op_aut(bool): None ++set_state_op_op(bool): None ++set_src_channel(bool): None ++set_src_ext_aut(bool): None ++set_src_ext_op(bool): None ++set_src_int_aut(bool): None ++set_src_int_op(bool): None + +OperationMode ++attributes: dict[str, Attribute] +-- ++set_state_channel(bool): None ++set_state_off_aut(bool): None ++set_state_op_aut(bool): None ++set_state_aut_aut(bool): None ++set_state_off_op(bool): None ++set_state_op_op(bool): None ++set_state_aut_op(bool): None ++is_opmode_off(): bool ++is_opmode_aut(): bool ++is_opmode_op(): bool + +SourceMode ++attributes: dict[str, Attribute] +-- ++set_src_channel(bool): None ++set_src_man_aut(bool): None ++set_src_man_op(bool): None ++set_src_int_aut(bool): None ++set_src_int_op(bool): None ++is_srcmode_int(): bool ++is_srcmode_man(): bool + +OperationSourceModeElement +-- + +AnaServParam ++op_src_mode: OperationSourceMode ++v_min: float ++v_max: float ++v_scl_min: float ++v_scl_max: float ++v_unit: int +-- ++set_v_op(float): None ++set_v_int(float): None ++set_v_ext(float): None ++set_v_req(float): None ++set_v_out(): None ++get_v_out(): float ++set_v_fbk(float): None + +BinServParam ++op_src_mode: OperationSourceMode ++v_state_0: str ++v_state_1: str +-- ++set_v_op(bool): None ++set_v_int(bool): None ++set_v_ext(bool): None ++set_v_req(bool): None ++set_v_out(): None ++get_v_out(): bool ++set_v_fbk(bool): None + +DIntServParam ++op_src_mode: OperationSourceMode ++v_min: int ++v_max: int ++v_scl_min: int ++v_scl_max: int ++v_unit: int +-- ++set_v_op(int): None ++set_v_int(int): None ++set_v_ext(int): None ++set_v_req(int): None ++set_v_out(): None ++get_v_out(): int ++set_v_fbk(int): None + +StringServParam ++op_src_mode: OperationSourceMode +-- ++set_v_op(str): None ++set_v_int(str): None ++set_v_ext(str): None ++set_v_req(str): None ++set_v_out(): None ++get_v_out(): str ++set_v_fbk(str): None + +ProcedureControl ++attributes: dict[str, Attribute] ++procedures: dict ++op_src_mode: OperationSourceMode ++default_procedure_id: int +-- ++set_procedure_op(int): None ++set_procedure_int(int): None ++set_procedure_ext(int): None ++set_procedure_req(int): None ++set_procedure_cur(): None ++get_procedure_cur(): int ++get_procedure_req(): int ++apply_procedure_parameters(): None + +Procedure ++procedure_parameters: dict[str, SUCParameterElement] ++process_value_ins: dict ++report_values: dict[str, SUCIndicatorElement] ++process_value_outs: dict[str, SUCIndicatorElement] +-- ++add_procedure_parameter(SUCParameterElement): None ++add_procedure_value_in(Any): None ++add_report_value(SUCIndicatorElement): None ++add_procedure_value_out(SUCIndicatorElement): None ++apply_procedure_parameters(): None + +Service ++exception: Exception ++op_src_mode: OperationSourceMode ++configuration_parameters: dict ++procedures: dict ++procedure_control: ProcedureControl ++state_machine: StateMachine ++thread_ctrl: ThreadControl +-- ++enable_pause_loop(bool): None ++enable_hold_loop(bool): None ++enable_restart(bool): None ++add_configuration_parameter(SUCParameterElement): None ++add_procedure(Procedure): None ++get_current_procedure(): Procedure ++is_state(str): bool ++get_state_stop_event(): Event ++idle(): None ++starting(): None ++execute(): None ++completing(): None ++completed(): None ++pausing(): None ++paused(): None ++resuming(): None ++holding(): None ++held(): None ++unholding(): None ++stopping(): None ++stopped(): None ++aborting(): None ++aborted(): None ++resetting(): None +-_init_idle_state(): None +-_state_change_callback(): None +-_state_change(): None +-_is_self_completing(): bool +-_apply_configuration_parameters(): None + +StateCodes ++stopped: int ++starting: int ++idle: int ++paused: int ++execute: int ++stopping: int ++aborting: int ++aborted: int ++holding: int ++held: int ++unholding: int ++pausing: int ++resuming: int ++resetting: int ++completing: int ++completed: int ++int_code: dict[int, str] +-- ++get_list_int(): list[int] ++get_list_str(): list[str] + +StateMachine ++attributes: dict[str, Attribute] ++op_src_mode: OperationSourceMode ++procedure_control: ProcedureControl ++execution_routine: callable ++command_en_ctrl: CommandEnControl ++act_state: int ++prev_state: int +-- ++set_command_op(int): None ++set_command_int(int): None ++set_command_ext(int): None ++command_execution(int): None ++start(): None ++restart(): None ++complete(): None ++pause(): None ++resume(): None ++reset(): None ++hold(): None ++unhold(): None ++stop(): None ++abort(): None ++state_change(): None ++update_command_en(): None ++get_current_state_str(): str ++disable_commands_if_no_procedure(int): None + +SUCDataAssembly ++tag_name: str ++tag_description: str ++attributes: dict[str, Attribute] +-- ++_add_attribute(Attribute): None + +SUCIndicatorElement +-- ++set_v(Any): None + +SUCOperationElement +-- ++set_v_int(Any): None ++get_v_out(): Any + +SUCActiveElement +-- + +SUCDiagnosticElement +-- + +SUCServiceElement +-- + +SUCServiceControl +-- + +SUCProcedureHealthView +-- + +SUCParameterElement +-- ++set_v_int(Any): None ++get_v_out(): Any ++set_v_fbk(Any): None + +SUCServiceProcedure +-- + +ThreadControl ++service_name: str ++state_change_function: Callable ++thread: StoppableThread ++running_state: str ++requested_state: str ++callback_function: Callable ++exception_callback: Callable[[Exception], None] +-- ++request_state(str, Callable): None ++reallocate_running_thread(): None ++run_thread(Callable): None ++stop_thread(bool): None + +StoppableThread ++stop_event: Event +-- ++stop(): None diff --git a/src/mtppy/active_elements.py b/src/mtppy/active_elements.py index 0f6b95f..3e0fb48 100644 --- a/src/mtppy/active_elements.py +++ b/src/mtppy/active_elements.py @@ -1,14 +1,128 @@ import logging import threading +from collections.abc import Callable from mtppy.attribute import Attribute -from mtppy.operation_source_mode import OperationSourceModeActiveElements +from mtppy.operation_source_mode import OperationSourceModeElement, OperationMode, OperationSourceMode from mtppy.suc_data_assembly import SUCActiveElement from time import sleep from simple_pid import PID +_logger = logging.getLogger(__name__) + + +class Locks(): + """Class that implements the locks for the active elements. Implements the locks for permission, interlock and protect.""" + + def __init__(self, perm_en=False, intl_en=False, prot_en=False, expext_save_pos_func: Callable[[], None] = None): + + self.perm_en = perm_en + self.intl_en = intl_en + self.prot_en = prot_en + + self._expect_save_pos = expext_save_pos_func + + self.permit = threading.Lock() + self.interlock = threading.Lock() + self.protect = threading.Lock() + + self.attributes = { + 'PermEn': Attribute('PermEn', bool, init_value=perm_en), + 'Permit': Attribute('Permit', bool, init_value=1, sub_cb=self.__set_permit), + 'IntlEn': Attribute('IntlEn', bool, init_value=intl_en), + 'Interlock': Attribute('Interlock', bool, init_value=1, sub_cb=self.__set_interlock), + 'ProtEn': Attribute('ProtEn', bool, init_value=prot_en), + 'Protect': Attribute('Protect', bool, init_value=1, sub_cb=self.__set_protect), + } + + def _run_allowed(self): + if self.attributes['PermEn'].value and self.attributes['Permit'].value == 0: + _logger.debug('Permission is not given') + return False + if self.attributes['IntlEn'].value and self.attributes['Interlock'].value == 0: + _logger.debug('Interlock is active') + return False + if self.attributes['ProtEn'].value and self.attributes['Protect'].value == 0: + _logger.debug('Protect is active') + return False + return True + + def __set_permit(self, value: bool): + """Callback function for Permit attribute.""" + _logger.debug(f'Permit set to {value}') + if self.attributes['Permit'].value == value: + return + self.set_permit(value) + + def set_permit(self, value: bool): + """Set Permit attribute with safety checks.""" + # don't set Permit to False if PermEn is False + if not self.attributes['PermEn'].value: + self.attributes['Permit'].set_value(True) + return + # only set value if it is different from the current one + if self.attributes['Permit'].value != value: + if not value: + self.permit.acquire(blocking=False) + else: + self.permit.release() + self.attributes['Permit'].set_value(value) + return + + def permit_status(self) -> bool: + return self.attributes['Permit'].value + + def __set_interlock(self, value: bool): + """Callback function for Interlock attribute.""" + _logger.debug(f'Interlock set to {value}') + if self.attributes['Interlock'].value == value: + return + self.set_interlock(value) + + def set_interlock(self, value: bool): + """Set Interlock attribute with safety checks.""" + # don't set Interlock to False if IntlEn is False + if not self.attributes['IntlEn'].value: + self.attributes['Interlock'].set_value(True) + return + if self.attributes['Interlock'].value != value: + if not value: + self.interlock.acquire(blocking=False) + else: + self.interlock.release() + self.attributes['Interlock'].set_value(value) + self._expect_save_pos() + + def interlock_status(self) -> bool: + return self.attributes['Interlock'].value + + def __set_protect(self, value: bool): + """Callback function for Protect attribute.""" + _logger.debug('Protect set to %s' % value) + if self.attributes['Protect'].value == value: + return + self.set_protect(value) + + def set_protect(self, value: bool, reset=False): + """Set Protect attribute with safety checks. + Reset should be True if called from reset_vlv function.""" + if not self.attributes['ProtEn'].value: + self.attributes['Protect'].set_value(True) + return + + if reset: + self.protect.release() + self.attributes['Protect'].set_value(True) + else: + self.protect.acquire(blocking=False) + self.attributes['Protect'].set_value(False) + self._expect_save_pos() + + def protect_status(self) -> bool: + return self.attributes['Protect'].value + class AnaVlv(SUCActiveElement): def __init__(self, tag_name, tag_description='', @@ -20,7 +134,9 @@ def __init__(self, tag_name, tag_description='', """ super().__init__(tag_name, tag_description) - self.op_src_mode = OperationSourceModeActiveElements() + self.op_src_mode = OperationSourceModeElement() + + self.locks = Locks(perm_en, intl_en, prot_en, self._expect_save_pos) self.pos_min = pos_min self.pos_max = pos_max @@ -33,13 +149,11 @@ def __init__(self, tag_name, tag_description='', self.safe_pos = safe_pos self.safe_pos_en = safe_pos_en - self.perm_en = perm_en - self.intl_en = intl_en - self.prot_en = prot_en self._add_attribute(Attribute('SafePos', bool, init_value=safe_pos)) self._add_attribute(Attribute('SafePosEn', bool, init_value=self.safe_pos_en)) - self._add_attribute(Attribute('SafePosAct', bool, init_value=False)) # default value should be true? + self._add_attribute(Attribute('SafePosAct', bool, init_value=False) + ) # default value should be true? self._add_attribute(Attribute('OpenAut', bool, init_value=0, sub_cb=self.set_open_aut)) self._add_attribute(Attribute('CloseAut', bool, init_value=0, sub_cb=self.set_close_aut)) self._add_attribute(Attribute('OpenOp', bool, init_value=0, sub_cb=self.set_open_op)) @@ -61,13 +175,7 @@ def __init__(self, tag_name, tag_description='', self._add_attribute(Attribute('CloseFbk', bool, init_value=False)) self._add_attribute(Attribute('PosFbkCalc', bool, init_value=pos_fbk_calc)) self._add_attribute(Attribute('PosFbk', float, init_value=pos_min)) - self._add_attribute(Attribute('PermEn', bool, init_value=perm_en)) - self._add_attribute(Attribute('Permit', bool, init_value=0)) - self._add_attribute(Attribute('IntlEn', bool, init_value=intl_en)) - self._add_attribute(Attribute('Interlock', bool, init_value=0)) - self._add_attribute(Attribute('ProtEn', bool, init_value=prot_en)) - self._add_attribute(Attribute('Protect', bool, init_value=0)) - self._add_attribute(Attribute('ResetOp', bool, init_value=0, sub_cb=self.set_reset_op)) + self._add_attribute(Attribute('ResetOp', bool, init_value=0, sub_cb=self._set_reset_op)) self._add_attribute(Attribute('ResetAut', bool, init_value=0, sub_cb=self.set_reset_aut)) def _expect_save_pos(self): @@ -76,9 +184,9 @@ def _expect_save_pos(self): else: if self.attributes['SafePosEn'].value: self._go_save_pos() + self.attributes['SafePosAct'].set_value(True) else: # hold position - logging.debug('Device has no safe position') - self.attributes['SafePosAct'].set_value(True) + _logger.debug('Device has no safe position') def _go_save_pos(self): if self.attributes['SafePos'].value == 1: @@ -90,59 +198,50 @@ def _go_save_pos(self): safe_pos = self.attributes['PosMin'].value self.attributes['Pos'].set_value(safe_pos) - logging.debug('Pos set to safe position %s' % safe_pos) + _logger.debug('Pos set to safe position %s' % safe_pos) if self.attributes['PosFbkCalc'].value: self.attributes['PosFbk'].set_value(safe_pos) def set_open_aut(self, value: bool): - logging.debug(f'OpenAut set to {value}') + _logger.debug(f'OpenAut set to {value}') if self.op_src_mode.attributes['StateAutAct'].value: if value and self._run_allowed(): self._run_open_vlv() def set_close_aut(self, value: bool): - logging.debug(f'CloseAut set to {value}') + _logger.debug(f'CloseAut set to {value}') if self.op_src_mode.attributes['StateAutAct'].value: if value and self._run_allowed(): self._run_close_vlv() def set_reset_aut(self, value: bool): - logging.debug(f'ResetAut set to {value}') + _logger.debug(f'ResetAut set to {value}') if self.op_src_mode.attributes['StateAutAct'].value and value: self._reset_vlv() def set_open_op(self, value: bool): - logging.debug(f'OpenOp set to {value}') + _logger.debug(f'OpenOp set to {value}') if self.op_src_mode.attributes['StateOpAct'].value: if value and self._run_allowed(): self._run_open_vlv() - self.attributes['OpenOp'].value = False + self.attributes['OpenOp'].set_value(False) def set_close_op(self, value: bool): - logging.debug(f'CloseOp set to {value}') + _logger.debug(f'CloseOp set to {value}') if self.op_src_mode.attributes['StateOpAct'].value: if value and self._run_allowed(): self._run_close_vlv() - self.attributes['CloseOp'].value = False + self.attributes['CloseOp'].set_value(False) - def set_reset_op(self, value: bool): - logging.debug(f'ResetOp set to {value}') + def _set_reset_op(self, value: bool): + _logger.debug(f'ResetOp set to {value}') if self.op_src_mode.attributes['StateOpAct'].value and value: self._reset_vlv() self.attributes['ResetOp'].set_value(False) def _run_allowed(self): - if self.attributes['PermEn'].value and self.attributes['Permit'].value == 0: - logging.debug(f'Permission is not given') - return False - if self.attributes['IntlEn'].value and self.attributes['Interlock'].value == 0: - logging.debug(f'Interlock is active') - return False - if self.attributes['ProtEn'].value and self.attributes['Protect'].value == 0: - logging.debug(f'Protect is active') - return False - return True + return self.locks._run_allowed() def _run_open_vlv(self): self.attributes['OpenAct'].set_value(True) @@ -161,8 +260,8 @@ def _run_close_vlv(self): self.attributes['CloseFbk'].set_value(True) def _reset_vlv(self): - if self.attributes['ProtEn'].value and self.attributes['Protect'].value == 0: - self.attributes['Protect'].set_value(True) + if self.locks.attributes['ProtEn'].value and self.locks.attributes['Protect'].value == 0: + self.locks.set_protect(True, reset=True) self.attributes['SafePosAct'].set_value(False) self.attributes['OpenAct'].set_value(False) if self.attributes['OpenFbkCalc']: @@ -172,12 +271,12 @@ def _reset_vlv(self): self.attributes['CloseFbk'].set_value(False) def set_pos_int(self, value: float): - logging.debug(f'PosInt set to {value}') + _logger.debug(f'PosInt set to {value}') if self.op_src_mode.attributes['SrcIntAct'].value: self._set_pos(value) def set_pos_man(self, value: float): - logging.debug(f'PosMan set to {value}') + _logger.debug(f'PosMan set to {value}') if self.op_src_mode.attributes['SrcManAct'].value: self._set_pos(value) @@ -197,7 +296,7 @@ def valid_value(self, value: float): def _set_pos(self, value: float): if not self._run_allowed(): - logging.debug('No position change is allowed') + _logger.debug('No position change is allowed') return if self.valid_value(value): @@ -209,60 +308,37 @@ def _set_pos(self, value: float): # if SafePosAct is active, safety setting for the position is accepted elif self.attributes['SafePosAct'].value or \ - (self.attributes['PermEn'].value and self.attributes['Permit'].value == 0): - logging.debug('manual or internal position specification inactive') + (self.locks.attributes['PermEn'].value and self.locks.attributes['Permit'].value == 0): + _logger.debug('manual or internal position specification inactive') return self.attributes['Pos'].set_value(value) - logging.debug('Pos set to %s' % value) + _logger.debug('Pos set to %s' % value) if self.attributes['PosFbkCalc'].value: self.attributes['PosFbk'].set_value(value) else: - logging.debug('Pos cannot be set to %s (out of range)' % value) + _logger.debug('Pos cannot be set to %s (out of range)' % value) def set_pos_rbk(self, value: float): corr_value = self._correct_value(value) self.attributes['PosRbk'].set_value(corr_value) - logging.debug(f'PosRbk set to {value}') + _logger.debug(f'PosRbk set to {value}') def set_pos_fbk(self, value: float): if not self.attributes['PosFbkCalc'].value: corr_value = self._correct_value(value) self.attributes['PosFbk'].set_value(corr_value) - logging.debug(f'PosFbk set to {value}') + _logger.debug(f'PosFbk set to {value}') def set_open_fbk(self, value: bool): if not self.attributes['OpenFbkCalc'].value: self.attributes['OpenFbk'].set_value(value) - logging.debug(f'OpenFbk set to {value}') + _logger.debug(f'OpenFbk set to {value}') def set_close_fbk(self, value: bool): if not self.attributes['CloseFbkCalc'].value: self.attributes['CloseFbk'].set_value(value) - logging.debug(f'CloseFbk set to {value}') - - def set_permit(self, value: bool): - if not self.attributes['PermEn'].value: - value = True - self.attributes['Permit'].set_value(value) - logging.debug('Permit set to %s' % value) - self.attributes['SafePosAct'].set_value(False) # safety position should not be activated for permit mode - - def set_interlock(self, value: bool): - if not self.attributes['IntlEn'].value: - value = True - self.attributes['Interlock'].set_value(value) - logging.debug('Interlock set to %s' % value) - self._expect_save_pos() - - def set_protect(self, value: bool): - if not self.attributes['ProtEn'].value: - value = True - if value: - self._reset_vlv() - self.attributes['Protect'].set_value(value) - logging.debug('Protect set to %s' % value) - self._expect_save_pos() + _logger.debug(f'CloseFbk set to {value}') def get_pos(self): return self.attributes['Pos'].value @@ -336,7 +412,7 @@ def _go_save_pos(self): safe_pos = self.attributes['PosMin'].value self.attributes['Pos'].set_value(safe_pos) - logging.debug('Pos set to safe position %s' % safe_pos) + _logger.debug('Pos set to safe position %s' % safe_pos) if self.attributes['MonEn'].value: self.monitored_values.pos = safe_pos self.monitor_pos_thread = threading.Thread(target=self.monitor_position_reached) @@ -392,14 +468,15 @@ def monitor_static_error(self): """ while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('static monitoring stopped') + _logger.debug('static monitoring stopped') break - states, control_signals = self.compare_states_control_signals(self.attributes['MonStatTi'].value) + states, control_signals = self.compare_states_control_signals( + self.attributes['MonStatTi'].value) if not all(states): # if the states of valve changed if all(control_signals): # but the control signals did not change self.attributes['MonStatErr'].set_value(True) - logging.debug('Static error set to True') + _logger.debug('Static error set to True') self._handle_monitored_error() def monitor_dynamic_error(self): @@ -408,14 +485,15 @@ def monitor_dynamic_error(self): """ while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('dynamic monitoring stopped') + _logger.debug('dynamic monitoring stopped') break - states, control_signals = self.compare_states_control_signals(self.attributes['MonDynTi'].value) + states, control_signals = self.compare_states_control_signals( + self.attributes['MonDynTi'].value) if all(states): # if the states of valve did not changed if not all(control_signals): # but a control command is executed self.attributes['MonDynErr'].set_value(True) - logging.debug('Dynamic error set to True') + _logger.debug('Dynamic error set to True') self._handle_monitored_error() def monitor_position_reached(self): @@ -428,29 +506,29 @@ def monitor_position_reached(self): self.attributes['PosReachedFbk'].set_value(PosReachedFbk) if not PosReachedFbk: self.attributes['MonPosErr'].set_value(True) - logging.debug('position error set to True') + _logger.debug('position error set to True') self._handle_monitored_error() def _handle_monitored_error(self): if self.attributes['MonSafePos']: - logging.debug('set valve to safety position') + _logger.debug('set valve to safety position') if self.attributes['SafePosEn'].value: self._go_save_pos() else: - logging.debug('Device has no safe position') + _logger.debug('Device has no safe position') self.attributes['SafePosAct'].set_value(True) def start_monitor(self): if self.attributes['MonEn'].value: self.monitor_static_thread = threading.Thread(target=self.monitor_static_error) self.monitor_static_thread.start() - logging.debug('static monitoring start') + _logger.debug('static monitoring start') self.monitor_dynamic_thread = threading.Thread(target=self.monitor_dynamic_error) self.monitor_dynamic_thread.start() - logging.debug('dynamic monitoring start') + _logger.debug('dynamic monitoring start') def set_open_aut(self, value: bool): - logging.debug('OpenAut set to %s' % value) + _logger.debug('OpenAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -466,11 +544,11 @@ def set_close_aut(self, value: bool): self.monitored_values.lock.acquire() self.monitored_values.close_aut = value self.monitored_values.lock.release() - logging.debug('CloseAut set to %s' % value) + _logger.debug('CloseAut set to %s' % value) self._run_close_vlv() def set_reset_aut(self, value: bool): - logging.debug('ResetAut set to %s' % value) + _logger.debug('ResetAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -479,7 +557,7 @@ def set_reset_aut(self, value: bool): self._reset_vlv() def set_open_op(self, value: bool): - logging.debug('OpenOp set to %s' % value) + _logger.debug('OpenOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -487,10 +565,10 @@ def set_open_op(self, value: bool): self.monitored_values.open_op = value self.monitored_values.lock.release() self._run_open_vlv() - self.attributes['OpenOp'].value = False + self.attributes['OpenOp'].set_value(False) def set_close_op(self, value: bool): - logging.debug('CloseOp set to %s' % value) + _logger.debug('CloseOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -498,10 +576,10 @@ def set_close_op(self, value: bool): self.monitored_values.close_op = value self.monitored_values.lock.release() self._run_close_vlv() - self.attributes['CloseOp'].value = False + self.attributes['CloseOp'].set_value(False) - def set_reset_op(self, value: bool): - logging.debug('ResetOp set to %s' % value) + def _set_reset_op(self, value: bool): + _logger.debug('ResetOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -521,11 +599,11 @@ def _set_pos(self, value: float): # if SafePosAct is active, safety setting for the position is accepted elif self.attributes['SafePosAct'].value or \ (self.attributes['PermEn'].value and self.attributes['Permit'].value == 0): - logging.debug('manual or internal position specification inactive') + _logger.debug('manual or internal position specification inactive') return self.attributes['Pos'].set_value(value) - logging.debug('Pos set to %s' % value) + _logger.debug('Pos set to %s' % value) # start monitor_pos_thread to after pos has been changed # if the position dose not change, there is no need to check the desired position @@ -542,37 +620,50 @@ def _set_pos(self, value: float): else: self.attributes['PosFbk'].set_value(value) else: - logging.debug('Pos cannot be set to %s (out of range)' % value) + _logger.debug('Pos cannot be set to %s (out of range)' % value) def set_stop_monitor(self): self.monitored_values.stop_event_lock.set() class BinVlv(SUCActiveElement): + """Binary Valve (BinVlv). Table 40 from VDI/VDE/NAMUR 2658-3.""" + def __init__(self, tag_name: str, tag_description: str = '', open_fbk_calc: bool = True, close_fbk_calc: bool = True, safe_pos: int = 0, safe_pos_en: bool = False, perm_en: bool = False, intl_en: bool = False, prot_en: bool = False): """ - Binary Valve (BinVlv). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. + Binary Valve (BinVlv). Initializes the attributes according to VDI/VDE/NAMUR 2658-3 Table 40. + + Args: + tag_name (str): Name of the Element. + tag_description (str): Description of the Element. + open_fbk_calc (bool): Open Feedback Source. 1: calculated, 0: sensor detection. + close_fbk_calc (bool): Close Feedback Source. 1: calculated, 0: sensor detection. + safe_pos (int): Safe Position. 1: open, 0: close. + safe_pos_en (bool): Hardware Safe Position enabled. 1: Device has a safe position, 0: Device has no safe position. + perm_en (bool): Enables the Permission Lock. 1: enabled, 0: disabled. + intl_en (bool): Enables the Interlock Lock. 1: enabled, 0: disabled. + prot_en (bool): Enables the Protection Lock. 1: enabled, 0: disabled. """ super().__init__(tag_name, tag_description) - self.op_src_mode = OperationSourceModeActiveElements() + self.op_src_mode = OperationMode() + + self.locks = Locks(perm_en, intl_en, prot_en, self._expect_save_pos) self.open_fbk_calc = open_fbk_calc self.close_fbk_calc = close_fbk_calc self.safe_pos = safe_pos self.safe_pos_en = safe_pos_en - self.perm_en = perm_en - self.intl_en = intl_en - self.prot_en = prot_en self._add_attribute(Attribute('SafePos', bool, init_value=safe_pos)) self._add_attribute(Attribute('SafePosEn', bool, init_value=self.safe_pos_en)) - self._add_attribute(Attribute('SafePosAct', bool, init_value=False)) # default value should be true? + self._add_attribute(Attribute('SafePosAct', bool, init_value=False) + ) # default value should be true? self._add_attribute(Attribute('OpenOp', bool, init_value=0, sub_cb=self.set_open_op)) self._add_attribute(Attribute('CloseOp', bool, init_value=0, sub_cb=self.set_close_op)) self._add_attribute(Attribute('OpenAut', bool, init_value=0, sub_cb=self.set_open_aut)) @@ -582,13 +673,7 @@ def __init__(self, tag_name: str, tag_description: str = '', open_fbk_calc: bool self._add_attribute(Attribute('OpenFbk', bool, init_value=False)) self._add_attribute(Attribute('CloseFbkCalc', bool, init_value=close_fbk_calc)) self._add_attribute(Attribute('CloseFbk', bool, init_value=False)) - self._add_attribute(Attribute('PermEn', bool, init_value=perm_en)) - self._add_attribute(Attribute('Permit', bool, init_value=0)) - self._add_attribute(Attribute('IntlEn', bool, init_value=intl_en)) - self._add_attribute(Attribute('Interlock', bool, init_value=0)) - self._add_attribute(Attribute('ProtEn', bool, init_value=prot_en)) - self._add_attribute(Attribute('Protect', bool, init_value=0)) - self._add_attribute(Attribute('ResetOp', bool, init_value=0, sub_cb=self.set_reset_op)) + self._add_attribute(Attribute('ResetOp', bool, init_value=0, sub_cb=self._set_reset_op)) self._add_attribute(Attribute('ResetAut', bool, init_value=0, sub_cb=self.set_reset_aut)) def _expect_save_pos(self): @@ -597,9 +682,9 @@ def _expect_save_pos(self): else: if self.attributes['SafePosEn'].value: self._go_safe_pos() + self.attributes['SafePosAct'].set_value(True) else: - logging.debug('Device has no safe position') - self.attributes['SafePosAct'].set_value(True) + _logger.debug('Device has no safe position') def _go_safe_pos(self): if self.attributes['SafePos'].value: @@ -608,53 +693,61 @@ def _go_safe_pos(self): self._run_close_vlv() def set_open_aut(self, value: bool): - logging.debug('OpenAut set to %s' % value) + _logger.debug('OpenAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value: if value and self._run_allowed(): self._run_open_vlv() def set_close_aut(self, value: bool): - logging.debug('CloseAut set to %s' % value) + _logger.debug('CloseAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value: - if value and self._run_allowed(): + if value and self._run_allowed(for_close=True): self._run_close_vlv() def set_reset_aut(self, value: bool): - logging.debug('ResetAut set to %s' % value) + _logger.debug('ResetAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: - self._reset_vlv() + self.reset_vlv() def set_open_op(self, value: bool): - logging.debug('OpenOp set to %s' % value) + _logger.debug('OpenOp set to %s' % value) + if self.attributes['OpenOp'].value == value: + return if self.op_src_mode.attributes['StateOpAct'].value: if value and self._run_allowed(): self._run_open_vlv() - self.attributes['OpenOp'].value = False + self.attributes['OpenOp'].set_value(False) def set_close_op(self, value: bool): - logging.debug('CloseOp set to %s' % value) + _logger.debug('CloseOp set to %s' % value) + if self.attributes['CloseOp'].value == value: + return if self.op_src_mode.attributes['StateOpAct'].value: - if value and self._run_allowed(): + if value and self._run_allowed(for_close=True): self._run_close_vlv() - self.attributes['CloseOp'].value = False + self.attributes['CloseOp'].set_value(False) - def set_reset_op(self, value: bool): - logging.debug('ResetOp set to %s' % value) + def _set_reset_op(self, value: bool): + _logger.debug('ResetOp set to %s' % value) + if self.attributes['ResetOp'].value == value: + return if self.op_src_mode.attributes['StateOpAct'].value and value: - self._reset_vlv() - self.attributes['ResetOp'].set_value(False) + self.reset_vlv() + self.attributes['ResetOp'].set_value(False) - def _run_allowed(self): - if self.attributes['PermEn'].value and self.attributes['Permit'].value == 0: - logging.debug('Permission is not given') - return False - if self.attributes['IntlEn'].value and self.attributes['Interlock'].value == 0: - logging.debug('Interlock is active') - return False - if self.attributes['ProtEn'].value and self.attributes['Protect'].value == 0: - logging.debug('Protect is active') - return False - return True + def _run_allowed(self, for_close: bool = False): + """ + Check if running the valve is allowed + + Args: + for_close (bool): If True, check if closing the valve is allowed. Default is False. + Returns: + bool: True if running is allowed, False otherwise. + """ + # Closing the valve is always allowed + if for_close: + return True + return self.locks._run_allowed() def _run_open_vlv(self): self.attributes['Ctrl'].set_value(True) @@ -672,9 +765,9 @@ def _run_close_vlv(self): if self.attributes['CloseFbkCalc']: self.attributes['CloseFbk'].set_value(True) - def _reset_vlv(self): - if self.attributes['ProtEn'].value and self.attributes['Protect'].value == 0: - self.attributes['Protect'].set_value(True) + def reset_vlv(self): + if self.locks.attributes['ProtEn'].value and self.locks.attributes['Protect'].value == 0: + self.locks.set_protect(True, reset=True) if self.attributes['SafePosEn'].value: self.attributes['SafePosAct'].set_value(False) @@ -689,35 +782,12 @@ def _reset_vlv(self): def set_open_fbk(self, value: bool): if not self.attributes['OpenFbkCalc'].value: self.attributes['OpenFbk'].set_value(value) - logging.debug('OpenFbk set to %s' % value) + _logger.debug('OpenFbk set to %s' % value) def set_close_fbk(self, value: bool): if not self.attributes['CloseFbkCalc'].value: self.attributes['CloseFbk'].set_value(value) - logging.debug('CloseFbk set to %s' % value) - - def set_permit(self, value: bool): - if not self.attributes['PermEn'].value: - value = True - self.attributes['Permit'].set_value(value) - logging.debug('Permit set to %s' % value) - self.attributes['SafePosAct'].set_value(False) - - def set_interlock(self, value: bool): - if not self.attributes['IntlEn'].value: - value = True - self.attributes['Interlock'].set_value(value) - logging.debug('Interlock set to %s' % value) - self._expect_save_pos() - - def set_protect(self, value: bool): - if not self.attributes['ProtEn'].value: - value = True - if value: - self._reset_vlv() - self.attributes['Protect'].set_value(value) - logging.debug('Protect set to %s' % value) - self._expect_save_pos() + _logger.debug('CloseFbk set to %s' % value) def get_open_fbk(self): return self.attributes['OpenFbk'].value @@ -725,6 +795,9 @@ def get_open_fbk(self): def get_close_fbk(self): return self.attributes['CloseFbk'].value + def get_ctrl(self): + return self.attributes['Ctrl'].value + class MonBinVlvValues: def __init__(self, open_aut, open_op, close_aut, close_op, reset_aut, reset_op): @@ -803,14 +876,15 @@ def monitor_static_error(self): """ while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('static monitoring stopped') + _logger.debug('static monitoring stopped') break - states, control_signals = self.compare_states_control_signals(self.attributes['MonStatTi'].value) + states, control_signals = self.compare_states_control_signals( + self.attributes['MonStatTi'].value) if not all(states): # if the states of valve changed if all(control_signals): # but the control signals did not change self.attributes['MonStatErr'].set_value(True) - logging.debug('Static error set to True') + _logger.debug('Static error set to True') self._handle_monitored_error() def monitor_dynamic_error(self): @@ -819,23 +893,24 @@ def monitor_dynamic_error(self): """ while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('dynamic monitoring stopped') + _logger.debug('dynamic monitoring stopped') break - states, control_signals = self.compare_states_control_signals(self.attributes['MonDynTi'].value) + states, control_signals = self.compare_states_control_signals( + self.attributes['MonDynTi'].value) if all(states): # if the states of valve did not changed if not all(control_signals): # but a control command is executed self.attributes['MonDynErr'].set_value(True) - logging.debug('Dynamic error set to True') + _logger.debug('Dynamic error set to True') self._handle_monitored_error() def _handle_monitored_error(self): if self.attributes['MonSafePos']: - logging.debug('set valve to safety position') + _logger.debug('set valve to safety position') if self.attributes['SafePosEn'].value: self._go_safe_pos() else: - logging.debug('Device has no safe position') + _logger.debug('Device has no safe position') self.attributes['SafePosAct'].set_value(True) def start_monitor(self): @@ -845,13 +920,13 @@ def start_monitor(self): if self.attributes['MonEn'].value: self.monitor_static_thread = threading.Thread(target=self.monitor_static_error) self.monitor_static_thread.start() - logging.debug('static monitoring start') + _logger.debug('static monitoring start') self.monitor_dynamic_thread = threading.Thread(target=self.monitor_dynamic_error) self.monitor_dynamic_thread.start() - logging.debug('dynamic monitoring start') + _logger.debug('dynamic monitoring start') def set_open_aut(self, value: bool): - logging.debug('OpenAut set to %s' % value) + _logger.debug('OpenAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -867,20 +942,20 @@ def set_close_aut(self, value: bool): self.monitored_values.lock.acquire() self.monitored_values.close_aut = value self.monitored_values.lock.release() - logging.debug('CloseAut set to %s' % value) + _logger.debug('CloseAut set to %s' % value) self._run_close_vlv() def set_reset_aut(self, value: bool): - logging.debug('ResetAut set to %s' % value) + _logger.debug('ResetAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() self.monitored_values.reset_aut = value self.monitored_values.lock.release() - self._reset_vlv() + self.reset_vlv() def set_open_op(self, value: bool): - logging.debug('OpenOp set to %s' % value) + _logger.debug('OpenOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -888,10 +963,10 @@ def set_open_op(self, value: bool): self.monitored_values.open_op = value self.monitored_values.lock.release() self._run_open_vlv() - self.attributes['OpenOp'].value = False + self.attributes['OpenOp'].set_value(False) def set_close_op(self, value: bool): - logging.debug('CloseOp set to %s' % value) + _logger.debug('CloseOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -899,16 +974,16 @@ def set_close_op(self, value: bool): self.monitored_values.close_op = value self.monitored_values.lock.release() self._run_close_vlv() - self.attributes['CloseOp'].value = False + self.attributes['CloseOp'].set_value(False) - def set_reset_op(self, value: bool): - logging.debug('ResetOp set to %s' % value) + def _set_reset_op(self, value: bool): + _logger.debug('ResetOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() self.monitored_values.reset_op = value self.monitored_values.lock.release() - self._reset_vlv() + self.reset_vlv() self.attributes['ResetOp'].set_value(False) def set_stop_monitor(self): @@ -920,7 +995,9 @@ def __init__(self, tag_name, tag_description='', rev_fbk_calc=True, fwd_fbk_calc rev_en=False, perm_en=False, intl_en=False, prot_en=False): super().__init__(tag_name, tag_description) - self.op_src_mode = OperationSourceModeActiveElements() + self.op_src_mode = OperationMode() + + self.locks = Locks(perm_en, intl_en, prot_en, self._expect_save_pos) self.rev_fbk_calc = rev_fbk_calc self.fwd_fbk_calc = fwd_fbk_calc @@ -928,12 +1005,10 @@ def __init__(self, tag_name, tag_description='', rev_fbk_calc=True, fwd_fbk_calc self.safe_pos = safe_pos self.fwd_en = fwd_en self.rev_en = rev_en - self.perm_en = perm_en - self.intl_en = intl_en - self.prot_en = prot_en self._add_attribute(Attribute('SafePos', bool, init_value=safe_pos)) - self._add_attribute(Attribute('SafePosAct', bool, init_value=False)) # default value should be true? + self._add_attribute(Attribute('SafePosAct', bool, init_value=False) + ) # default value should be true? self._add_attribute(Attribute('FwdEn', bool, init_value=fwd_en)) self._add_attribute(Attribute('RevEn', bool, init_value=rev_en)) self._add_attribute(Attribute('StopOp', bool, init_value=0, sub_cb=self.set_stop_op)) @@ -949,13 +1024,7 @@ def __init__(self, tag_name, tag_description='', rev_fbk_calc=True, fwd_fbk_calc self._add_attribute(Attribute('FwdFbkCalc', bool, init_value=fwd_fbk_calc)) self._add_attribute(Attribute('FwdFbk', bool, init_value=False)) self._add_attribute(Attribute('Trip', bool, init_value=True)) - self._add_attribute(Attribute('PermEn', bool, init_value=perm_en)) - self._add_attribute(Attribute('Permit', bool, init_value=0)) - self._add_attribute(Attribute('IntlEn', bool, init_value=intl_en)) - self._add_attribute(Attribute('Interlock', bool, init_value=0)) - self._add_attribute(Attribute('ProtEn', bool, init_value=prot_en)) - self._add_attribute(Attribute('Protect', bool, init_value=0)) - self._add_attribute(Attribute('ResetOp', bool, init_value=0, sub_cb=self.set_reset_op)) + self._add_attribute(Attribute('ResetOp', bool, init_value=0, sub_cb=self._set_reset_op)) self._add_attribute(Attribute('ResetAut', bool, init_value=0, sub_cb=self.set_reset_aut)) def _expect_save_pos(self): @@ -972,68 +1041,50 @@ def _go_safe_pos(self): self._stop_drv() def _run_allowed(self): - if self.attributes['PermEn'].value and self.attributes['Permit'].value == 0: - logging.debug('Permission is not given') - return False - if self.attributes['IntlEn'].value and self.attributes['Interlock'].value == 0: - logging.debug('Interlock is active') - return False - if self.attributes['ProtEn'].value and self.attributes['Protect'].value == 0: - logging.debug('Protect is active') + if not self.locks._run_allowed(): return False if not self.attributes['Trip'].value: - logging.debug('tripped') + _logger.debug('tripped') return False return True def set_fwd_op(self, value: bool): - logging.debug('FwdOp set to %s' % value) + _logger.debug('FwdOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and self.fwd_en: if value and self._run_allowed(): self._run_fwd_drv() - self.attributes['FwdOp'].value = False + self.attributes['FwdOp'].set_value(False) def set_rev_op(self, value: bool): - logging.debug('RevOp set to %s' % value) + _logger.debug('RevOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and self.rev_en: if value and self._run_allowed(): self._run_rev_drv() - self.attributes['RevOp'].value = False + self.attributes['RevOp'].set_value(False) def set_stop_op(self, value: bool): - logging.debug('StopOp set to %s' % value) + _logger.debug('StopOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and value: self._stop_drv() self.attributes['StopOp'].set_value(False) - def set_reset_op(self, value: bool): - logging.debug('ResetOp set to %s' % value) - if self.op_src_mode.attributes['StateOpAct'].value and value: - self._reset_drv() - self.attributes['ResetOp'].set_value(False) - def set_fwd_aut(self, value: bool): - logging.debug('FwdAut set to %s' % value) + _logger.debug('FwdAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.fwd_en: if value and self._run_allowed(): self._run_fwd_drv() def set_rev_aut(self, value: bool): - logging.debug('RevAut set to %s' % value) + _logger.debug('RevAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.rev_en: if value and self._run_allowed(): self._run_rev_drv() def set_stop_aut(self, value: bool): - logging.debug('StopAut set to %s' % value) + _logger.debug('StopAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: self._stop_drv() - def set_reset_aut(self, value: bool): - logging.debug('ResetAut set to %s' % value) - if self.op_src_mode.attributes['StateAutAct'].value and value: - self._reset_drv() - def _run_fwd_drv(self): self.attributes['FwdCtrl'].set_value(True) if self.attributes['FwdFbkCalc']: @@ -1059,47 +1110,24 @@ def _stop_drv(self): self.attributes['RevFbk'].set_value(False) def _reset_drv(self): - if self.attributes['ProtEn'].value and self.attributes['Protect'].value == 0: - self.attributes['Protect'].set_value(True) + if self.locks.attributes['ProtEn'].value and self.locks.attributes['Protect'].value == 0: + self.locks.set_protect(True, reset=True) self.attributes['SafePosAct'].set_value(False) self._stop_drv() def set_rev_fbk(self, value: bool): if not self.attributes['RevFbkCalc'].value: self.attributes['RevFbk'].set_value(value) - logging.debug('RevFbk set to %s' % value) + _logger.debug('RevFbk set to %s' % value) def set_fwd_fbk(self, value: bool): if not self.attributes['FwdFbkCalc'].value: self.attributes['FwdFbk'].set_value(value) - logging.debug('FwdFbk set to %s' % value) + _logger.debug('FwdFbk set to %s' % value) def set_trip(self, value: bool): self.attributes['Trip'].set_value(value) - logging.debug('Trip set to %s' % value) - self._expect_save_pos() - - def set_permit(self, value: bool): - if not self.attributes['PermEn'].value: - value = True - self.attributes['Permit'].set_value(value) - logging.debug('Permit set to %s' % value) - self.attributes['SafePosAct'].set_value(False) - - def set_interlock(self, value: bool): - if not self.attributes['IntlEn'].value: - value = True - self.attributes['Interlock'].set_value(value) - logging.debug('Interlock set to %s' % value) - self._expect_save_pos() - - def set_protect(self, value: bool): - if not self.attributes['ProtEn'].value: - value = True - if value: - self._reset_drv() - self.attributes['Protect'].set_value(value) - logging.debug('Protect set to %s' % value) + _logger.debug('Trip set to %s' % value) self._expect_save_pos() def get_fwd_fbk(self): @@ -1187,31 +1215,33 @@ def compare_states_control_signals(self, monitor_time): def monitor_static_error(self): while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('static monitoring stopped') + _logger.debug('static monitoring stopped') break - states, control_signals = self.compare_states_control_signals(self.attributes['MonStatTi'].value) + states, control_signals = self.compare_states_control_signals( + self.attributes['MonStatTi'].value) if not all(states): if all(control_signals): self.attributes['MonStatErr'].set_value(True) - logging.debug('Static error set to True') + _logger.debug('Static error set to True') self._handle_monitored_error() def monitor_dynamic_error(self): while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('dynamic monitoring stopped') + _logger.debug('dynamic monitoring stopped') break - states, control_signals = self.compare_states_control_signals(self.attributes['MonDynTi'].value) + states, control_signals = self.compare_states_control_signals( + self.attributes['MonDynTi'].value) if all(states): if not all(control_signals): self.attributes['MonDynErr'].set_value(True) - logging.debug('Dynamic error set to True') + _logger.debug('Dynamic error set to True') self._handle_monitored_error() def _handle_monitored_error(self): - logging.debug('set valve to safety position') + _logger.debug('set valve to safety position') self._go_safe_pos() self.attributes['SafePosAct'].set_value(True) @@ -1219,13 +1249,13 @@ def start_monitor(self): if self.attributes['MonEn'].value: self.monitor_static_thread = threading.Thread(target=self.monitor_static_error) self.monitor_static_thread.start() - logging.debug('static monitoring start') + _logger.debug('static monitoring start') self.monitor_dynamic_thread = threading.Thread(target=self.monitor_dynamic_error) self.monitor_dynamic_thread.start() - logging.debug('dynamic monitoring start') + _logger.debug('dynamic monitoring start') def set_fwd_op(self, value: bool): - logging.debug('FwdOp set to %s' % value) + _logger.debug('FwdOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and self.fwd_en: if value and self._run_allowed(): self._run_fwd_drv() @@ -1233,10 +1263,10 @@ def set_fwd_op(self, value: bool): self.monitored_values.lock.acquire() self.monitored_values.fwd_op = value self.monitored_values.lock.release() - self.attributes['FwdOp'].value = False + self.attributes['FwdOp'].set_value(False) def set_rev_op(self, value: bool): - logging.debug('RevOp set to %s' % value) + _logger.debug('RevOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and self.rev_en: if value and self._run_allowed(): @@ -1245,10 +1275,10 @@ def set_rev_op(self, value: bool): self.monitored_values.rev_op = value self.monitored_values.lock.release() self._run_rev_drv() - self.attributes['RevOp'].value = False + self.attributes['RevOp'].set_value(False) def set_stop_op(self, value: bool): - logging.debug('StopOp set to %s' % value) + _logger.debug('StopOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -1257,8 +1287,8 @@ def set_stop_op(self, value: bool): self._stop_drv() self.attributes['StopOp'].set_value(False) - def set_reset_op(self, value: bool): - logging.debug('ResetOp set to %s' % value) + def _set_reset_op(self, value: bool): + _logger.debug('ResetOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -1268,7 +1298,7 @@ def set_reset_op(self, value: bool): self.attributes['ResetOp'].set_value(False) def set_fwd_aut(self, value: bool): - logging.debug('FwdAut set to %s' % value) + _logger.debug('FwdAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.fwd_en: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -1278,7 +1308,7 @@ def set_fwd_aut(self, value: bool): self._run_fwd_drv() def set_rev_aut(self, value: bool): - logging.debug('RevAut set to %s' % value) + _logger.debug('RevAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.rev_en: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -1288,7 +1318,7 @@ def set_rev_aut(self, value: bool): self._run_rev_drv() def set_stop_aut(self, value: bool): - logging.debug('StopAut set to %s' % value) + _logger.debug('StopAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -1297,7 +1327,7 @@ def set_stop_aut(self, value: bool): self._stop_drv() def set_reset_aut(self, value: bool): - logging.debug('ResetAut set to %s' % value) + _logger.debug('ResetAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -1319,9 +1349,11 @@ def __init__(self, tag_name: str, tag_description: str = '', """ Analog Drive (AnaDrv). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. """ - super().__init__(tag_name, tag_description) + super().__init__(tag_name, tag_description, perm_en, intl_en, prot_en) - self.op_src_mode = OperationSourceModeActiveElements() + self.op_src_mode = OperationSourceModeElement() + + self.locks = Locks(perm_en, intl_en, prot_en, self._expect_save_pos) self.rpm_min = rpm_min self.rpm_max = rpm_max @@ -1335,9 +1367,6 @@ def __init__(self, tag_name: str, tag_description: str = '', self.safe_pos = safe_pos self.fwd_en = fwd_en self.rev_en = rev_en - self.perm_en = perm_en - self.intl_en = intl_en - self.prot_en = prot_en self._add_attribute(Attribute('SafePos', bool, init_value=safe_pos)) self._add_attribute(Attribute('SafePosAct', bool, init_value=True)) @@ -1367,13 +1396,7 @@ def __init__(self, tag_name: str, tag_description: str = '', self._add_attribute(Attribute('RpmFbkCalc', bool, init_value=rpm_fbk_calc)) self._add_attribute(Attribute('RpmFbk', float, init_value=rpm_min)) self._add_attribute(Attribute('Trip', bool, init_value=True)) - self._add_attribute(Attribute('PermEn', bool, init_value=perm_en)) - self._add_attribute(Attribute('Permit', bool, init_value=0)) - self._add_attribute(Attribute('IntlEn', bool, init_value=intl_en)) - self._add_attribute(Attribute('Interlock', bool, init_value=0)) - self._add_attribute(Attribute('ProtEn', bool, init_value=prot_en)) - self._add_attribute(Attribute('Protect', bool, init_value=0)) - self._add_attribute(Attribute('ResetOp', bool, init_value=0, sub_cb=self.set_reset_op)) + self._add_attribute(Attribute('ResetOp', bool, init_value=0, sub_cb=self._set_reset_op)) self._add_attribute(Attribute('ResetAut', bool, init_value=0, sub_cb=self.set_reset_aut)) def _expect_save_pos(self): @@ -1390,65 +1413,58 @@ def _go_save_pos(self): self._stop_drv() def _run_allowed(self): - if self.attributes['PermEn'].value and self.attributes['Permit'].value == 0: - logging.debug('Permission is not given') - return False - if self.attributes['IntlEn'].value and self.attributes['Interlock'].value == 0: - logging.debug('Interlock is active') - return False - if self.attributes['ProtEn'].value and self.attributes['Protect'].value == 0: - logging.debug('Protect is active') + if not self.locks._run_allowed(): return False if not self.attributes['Trip'].value: - logging.debug('Drive protection triggered') + _logger.debug('Drive protection triggered') return False return True def set_fwd_op(self, value: bool): - logging.debug('FwdOp set to %s' % value) + _logger.debug('FwdOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and self.fwd_en: if value and self._run_allowed(): self._run_fwd_drv() - self.attributes['FwdOp'].value = False + self.attributes['FwdOp'].set_value(False) def set_rev_op(self, value: bool): - logging.debug('RevOp set to %s' % value) + _logger.debug('RevOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and self.rev_en: if value and self._run_allowed(): self._run_rev_drv() - self.attributes['RevOp'].value = False + self.attributes['RevOp'].set_value(False) def set_stop_op(self, value: bool): - logging.debug('StopOp set to %s' % value) + _logger.debug('StopOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and value: self._stop_drv() self.attributes['StopOp'].set_value(False) - def set_reset_op(self, value: bool): - logging.debug('ResetOp set to %s' % value) + def _set_reset_op(self, value: bool): + _logger.debug('ResetOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and value: self._reset_drv() self.attributes['ResetOp'].set_value(False) def set_fwd_aut(self, value: bool): - logging.debug('FwdAut set to %s' % value) + _logger.debug('FwdAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.fwd_en: if value and self._run_allowed(): self._run_fwd_drv() def set_rev_aut(self, value: bool): - logging.debug('RevAut set to %s' % value) + _logger.debug('RevAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.rev_en: if value and self._run_allowed(): self._run_rev_drv() def set_stop_aut(self, value: bool): - logging.debug('StopAut set to %s' % value) + _logger.debug('StopAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: self._stop_drv() def set_reset_aut(self, value: bool): - logging.debug('ResetAut set to %s' % value) + _logger.debug('ResetAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: self._reset_drv() @@ -1477,18 +1493,18 @@ def _stop_drv(self): self.attributes['RevFbk'].set_value(False) def _reset_drv(self): - if self.attributes['ProtEn'].value and self.attributes['Protect'].value == 0: - self.attributes['Protect'].set_value(True) + if self.locks.attributes['ProtEn'].value and self.locks.attributes['Protect'].value == 0: + self.locks.set_protect(True, reset=True) self.attributes['SafePosAct'].set_value(False) self._stop_drv() def set_rpm_int(self, value: float): - logging.debug('RpmInt set to %s' % value) + _logger.debug('RpmInt set to %s' % value) if self.op_src_mode.attributes['SrcIntAct'].value: self._set_rpm(value) def set_rpm_man(self, value: float): - logging.debug('RpmIntMan set to %s' % value) + _logger.debug('RpmIntMan set to %s' % value) if self.op_src_mode.attributes['SrcManAct'].value: self._set_rpm(value) @@ -1509,59 +1525,36 @@ def _correct_value(self, value: float): def _set_rpm(self, value: float): if self.valid_value(value): self.attributes['Rpm'].set_value(value) - logging.debug('Rpm set to %s' % value) + _logger.debug('Rpm set to %s' % value) if self.attributes['RpmFbkCalc'].value: self.attributes['RpmFbk'].set_value(value) else: - logging.debug('Rpm cannot be set to %s (out of range)' % value) + _logger.debug('Rpm cannot be set to %s (out of range)' % value) def set_rpm_rbk(self, value: float): corr_value = self._correct_value(value) self.attributes['RpmRbk'].set_value(corr_value) - logging.debug('RpmRbk set to %s' % corr_value) + _logger.debug('RpmRbk set to %s' % corr_value) def set_rpm_fbk(self, value: float): if not self.attributes['RpmFbkCalc'].value: corr_value = self._correct_value(value) self.attributes['RpmFbk'].set_value(corr_value) - logging.debug('RpmFbk set to %s' % corr_value) + _logger.debug('RpmFbk set to %s' % corr_value) def set_rev_fbk(self, value: bool): if not self.attributes['RevFbkCalc'].value: self.attributes['RevFbk'].set_value(value) - logging.debug('RevFbk set to %s' % value) + _logger.debug('RevFbk set to %s' % value) def set_fwd_fbk(self, value: bool): if not self.attributes['FwdFbkCalc'].value: self.attributes['FwdFbk'].set_value(value) - logging.debug('FwdFbk set to %s' % value) + _logger.debug('FwdFbk set to %s' % value) def set_trip(self, value: bool): self.attributes['Trip'].set_value(value) - logging.debug('Trip set to %s' % value) - self._expect_save_pos() - - def set_permit(self, value: bool): - if not self.attributes['PermEn'].value: - value = True - self.attributes['Permit'].set_value(value) - logging.debug('Permit set to %s' % value) - self._expect_save_pos() - - def set_interlock(self, value: bool): - if not self.attributes['IntlEn'].value: - value = True - self.attributes['Interlock'].set_value(value) - logging.debug('Interlock set to %s' % value) - self._expect_save_pos() - - def set_protect(self, value: bool): - if not self.attributes['ProtEn'].value: - value = True - if value: - self._reset_drv() - self.attributes['Protect'].set_value(value) - logging.debug('Protect set to %s' % value) + _logger.debug('Trip set to %s' % value) self._expect_save_pos() def get_rpm(self): @@ -1675,38 +1668,40 @@ def compare_states_control_signals(self, monitor_time): def monitor_static_error(self): while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('static monitoring stopped') + _logger.debug('static monitoring stopped') break - states, control_signals = self.compare_states_control_signals(self.attributes['MonStatTi'].value) + states, control_signals = self.compare_states_control_signals( + self.attributes['MonStatTi'].value) if not all(states): if all(control_signals): self.attributes['MonStatErr'].set_value(True) - logging.debug('Static error set to True') + _logger.debug('Static error set to True') self._handle_monitored_error() def monitor_dynamic_error(self): while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('dynamic monitoring stopped') + _logger.debug('dynamic monitoring stopped') break - states, control_signals = self.compare_states_control_signals(self.attributes['MonDynTi'].value) + states, control_signals = self.compare_states_control_signals( + self.attributes['MonDynTi'].value) if all(states): if not all(control_signals): self.attributes['MonDynErr'].set_value(True) - logging.debug('Dynamic error set to True') + _logger.debug('Dynamic error set to True') self._handle_monitored_error() def monitor_rpm_error(self): while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('rpm monitoring stopped') + _logger.debug('rpm monitoring stopped') break rpm_error = self.monitored_values.rpm - self.attributes['RpmFbk'].value if rpm_error: self.attributes['RpmErr'].set_value(rpm_error) - logging.debug(f'Rpm error set to {rpm_error}') + _logger.debug(f'Rpm error set to {rpm_error}') self._handle_monitored_error() sleep(0.01) @@ -1714,31 +1709,31 @@ def monitor_rpm_error(self): def monitor_rpm_high_limit(self): while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('rpm high limit monitoring stopped') + _logger.debug('rpm high limit monitoring stopped') break self.monitored_values.lock.acquire() high_limit_alarm = self.attributes['RpmFbk'].value > self.attributes['RpmAHLim'].value self.monitored_values.lock.release() if high_limit_alarm: self.attributes['RpmAHAct'].set_value(True) - logging.debug('Rpm limit high set to True') + _logger.debug('Rpm limit high set to True') sleep(0.01) def monitor_rpm_low_limit(self): while True: if self.monitored_values.stop_event_lock.is_set(): - logging.debug('rpm low limit monitoring stopped') + _logger.debug('rpm low limit monitoring stopped') break self.monitored_values.lock.acquire() low_limit_alarm = self.attributes['RpmFbk'].value < self.attributes['RpmALLim'].value self.monitored_values.lock.release() if low_limit_alarm: self.attributes['RpmALAct'].set_value(True) - logging.debug('Rpm limit low set to True') + _logger.debug('Rpm limit low set to True') sleep(0.01) def _handle_monitored_error(self): - logging.debug('set valve to safety position') + _logger.debug('set valve to safety position') self._go_save_pos() self.attributes['SafePosAct'].set_value(True) @@ -1746,28 +1741,29 @@ def start_monitor(self): if self.attributes['MonEn'].value: self.monitor_static_thread = threading.Thread(target=self.monitor_static_error) self.monitor_static_thread.start() - logging.debug('static monitoring start') + _logger.debug('static monitoring start') self.monitor_dynamic_thread = threading.Thread(target=self.monitor_dynamic_error) self.monitor_dynamic_thread.start() - logging.debug('dynamic monitoring start') + _logger.debug('dynamic monitoring start') self.monitor_rpm_error_thread = threading.Thread(target=self.monitor_rpm_error) self.monitor_rpm_error_thread.start() - logging.debug('rpm error monitoring start') + _logger.debug('rpm error monitoring start') if self.attributes['RpmAHEn'].value: - self.monitor_rpm_limit_high_thread = threading.Thread(target=self.monitor_rpm_high_limit) + self.monitor_rpm_limit_high_thread = threading.Thread( + target=self.monitor_rpm_high_limit) self.monitor_rpm_limit_high_thread.start() - logging.debug('rpm high limit monitoring start') + _logger.debug('rpm high limit monitoring start') if self.attributes['RpmALEn'].value: self.monitor_rpm_limit_low_thread = threading.Thread(target=self.monitor_rpm_low_limit) self.monitor_rpm_limit_low_thread.start() - logging.debug('rpm low limit monitoring start') + _logger.debug('rpm low limit monitoring start') def set_fwd_op(self, value: bool): - logging.debug('FwdOp set to %s' % value) + _logger.debug('FwdOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and self.fwd_en: if value and self._run_allowed(): self._run_fwd_drv() @@ -1775,10 +1771,10 @@ def set_fwd_op(self, value: bool): self.monitored_values.lock.acquire() self.monitored_values.fwd_op = value self.monitored_values.lock.release() - self.attributes['FwdOp'].value = False + self.attributes['FwdOp'].set_value(False) def set_rev_op(self, value: bool): - logging.debug('RevOp set to %s' % value) + _logger.debug('RevOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and self.rev_en: if value and self._run_allowed(): @@ -1787,10 +1783,10 @@ def set_rev_op(self, value: bool): self.monitored_values.rev_op = value self.monitored_values.lock.release() self._run_rev_drv() - self.attributes['RevOp'].value = False + self.attributes['RevOp'].set_value(False) def set_stop_op(self, value: bool): - logging.debug('StopOp set to %s' % value) + _logger.debug('StopOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -1799,8 +1795,8 @@ def set_stop_op(self, value: bool): self._stop_drv() self.attributes['StopOp'].set_value(False) - def set_reset_op(self, value: bool): - logging.debug('ResetOp set to %s' % value) + def _set_reset_op(self, value: bool): + _logger.debug('ResetOp set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -1810,7 +1806,7 @@ def set_reset_op(self, value: bool): self.attributes['ResetOp'].set_value(False) def set_fwd_aut(self, value: bool): - logging.debug('FwdAut set to %s' % value) + _logger.debug('FwdAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.fwd_en: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -1820,7 +1816,7 @@ def set_fwd_aut(self, value: bool): self._run_fwd_drv() def set_rev_aut(self, value: bool): - logging.debug('RevAut set to %s' % value) + _logger.debug('RevAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.rev_en: if value and self._run_allowed(): if self.attributes['MonEn'].value: @@ -1830,7 +1826,7 @@ def set_rev_aut(self, value: bool): self._run_rev_drv() def set_stop_aut(self, value: bool): - logging.debug('StopAut set to %s' % value) + _logger.debug('StopAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -1839,7 +1835,7 @@ def set_stop_aut(self, value: bool): self._stop_drv() def set_reset_aut(self, value: bool): - logging.debug('ResetAut set to %s' % value) + _logger.debug('ResetAut set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and value: if self.attributes['MonEn'].value: self.monitored_values.lock.acquire() @@ -1854,7 +1850,7 @@ def _set_rpm(self, value: float): self.monitored_values.lock.acquire() self.monitored_values.rpm = value self.monitored_values.lock.release() - logging.debug('Rpm set to %s' % value) + _logger.debug('Rpm set to %s' % value) if self.attributes['RpmFbkCalc'].value: if self.attributes['MonEn'].value: @@ -1865,7 +1861,7 @@ def _set_rpm(self, value: float): self.attributes['RpmFbk'].set_value(value) else: - logging.debug('Rpm cannot be set to %s (out of range)' % value) + _logger.debug('Rpm cannot be set to %s (out of range)' % value) def set_stop_monitor(self): self.monitored_values.stop_event_lock.set() @@ -1883,7 +1879,8 @@ def __init__(self, mv_init_value=0, kp=100, ki=10, kd=1, mv_min=0, mv_max=100, s self.mv = mv_init_value self.sample_time = sample_time - self.ctrl = PID(Kp=self.kp, Ki=self.ki, Kd=self.kd, output_limits=(mv_min, mv_max), sample_time=sample_time) + self.ctrl = PID(Kp=self.kp, Ki=self.ki, Kd=self.kd, + output_limits=(mv_min, mv_max), sample_time=sample_time) self.thread = threading.Thread(target=self.loop) self.stop_flag = True @@ -1893,21 +1890,21 @@ def set_limits(self, mv_min, mv_max): self.ctrl.output_limits = (mv_min, mv_max) def start(self): - logging.debug('Starting PID controller...') + _logger.debug('Starting PID controller...') self.ctrl.setpoint = self.sp self.stopped = False self.stop_flag = False self.thread.start() - logging.debug('Started') + _logger.debug('Started') def stop(self): - logging.debug('Stopping PID controller...') + _logger.debug('Stopping PID controller...') self.stopped = False self.stop_flag = True while not self.stopped: sleep(0.1) self.ctrl.reset() - logging.debug('Stopped') + _logger.debug('Stopped') def set_sp(self, sp): self.sp = sp @@ -1944,7 +1941,7 @@ def __init__(self, tag_name, tag_description='', P=100, Ti=10, Td=1): super().__init__(tag_name, tag_description) - self.op_src_mode = OperationSourceModeActiveElements() + self.op_src_mode = OperationSourceMode() self.pv_scl_min = pv_scl_min self.pv_scl_max = pv_scl_max @@ -1972,8 +1969,10 @@ def __init__(self, tag_name, tag_description='', self._add_attribute(Attribute('PVSclMin', float, init_value=pv_scl_min)) self._add_attribute(Attribute('PVSclMax', float, init_value=pv_scl_max)) self._add_attribute(Attribute('PVUnit', int, init_value=pv_unit)) - self._add_attribute(Attribute('SPMan', float, init_value=sp_man_min, sub_cb=self.set_sp_man)) - self._add_attribute(Attribute('SPInt', float, init_value=sp_int_min, sub_cb=self.set_sp_int)) + self._add_attribute( + Attribute('SPMan', float, init_value=sp_man_min, sub_cb=self.set_sp_man)) + self._add_attribute( + Attribute('SPInt', float, init_value=sp_int_min, sub_cb=self.set_sp_int)) self._add_attribute(Attribute('SPSclMin', float, init_value=sp_scl_min)) self._add_attribute(Attribute('SPSclMax', float, init_value=sp_scl_max)) self._add_attribute(Attribute('SPUnit', int, init_value=sp_unit)) @@ -2022,31 +2021,31 @@ def _set_sp(self, value, v_min, v_max): if self._valid_value(value, v_min, v_max): self.attributes['SP'].set_value(value) self.ctrl.set_sp(value) - logging.debug('SP set to %s' % value) + _logger.debug('SP set to %s' % value) else: - logging.debug('SP cannot be set to %s (out of range)' % value) + _logger.debug('SP cannot be set to %s (out of range)' % value) def set_sp_man(self, value): - logging.debug('SPMan set to %s' % value) + _logger.debug('SPMan set to %s' % value) if self.op_src_mode.attributes['SrcManAct'].value and self.op_src_mode.attributes['StateAutAct'].value: self._set_sp(value, self.sp_man_min, self.sp_man_max) def set_sp_int(self, value): - logging.debug('SPInt set to %s' % value) + _logger.debug('SPInt set to %s' % value) if self.op_src_mode.attributes['SrcIntAct'].value and self.op_src_mode.attributes['StateAutAct'].value: self._set_sp(value, self.sp_int_min, self.sp_int_max) def set_mv_man(self, value): - logging.debug('MVMan set to %s' % value) + _logger.debug('MVMan set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value: self._set_mv(value, self.mv_min, self.mv_max) def _set_mv(self, value, v_min, v_max): if self._valid_value(value, v_min, v_max): self.attributes['MV'].set_value(value) - logging.debug('MV set to %s' % value) + _logger.debug('MV set to %s' % value) else: - logging.debug('MV cannot be set to %s (out of range)' % value) + _logger.debug('MV cannot be set to %s (out of range)' % value) def _update_mv_int_loop(self): while not self._stop_update_mv_flag: diff --git a/src/mtppy/attribute.py b/src/mtppy/attribute.py index 5a59873..8942734 100644 --- a/src/mtppy/attribute.py +++ b/src/mtppy/attribute.py @@ -1,15 +1,24 @@ import logging +from collections.abc import Callable +from typing import Any + + +_logger = logging.getLogger(__name__) + class Attribute: - def __init__(self, name: str, data_type, init_value, sub_cb=None): + def __init__(self, name: str, data_type, init_value, sub_cb: Callable[[Any], None] = None): """ - Atttribute represents an elementary object (attribute or parameter) of a data assembly. Depending on whether + Attribute represents an elementary object (attribute or parameter) of a data assembly. Depending on whether subscription callback is defined or not, it might be an monitored object or a static OPC UA node. - :param name: Attribute name. - :param data_type: Attribute type. - :param init_value: Initial value of the attribute. - :param sub_cb: Subscription callback if applied. + + Args: + name (str): Attribute name. + data_type (type): Attribute type. + init_value (type): Initial value of the attribute. + sub_cb (Callable[[Any], None], optional): Subscription callback. + If defined, the attribute will be monitored. """ self.name = name self.type = data_type @@ -17,13 +26,17 @@ def __init__(self, name: str, data_type, init_value, sub_cb=None): self.init_value = corrected_value self.value = corrected_value self.comm_obj = None - self.sub_cb = sub_cb + self.sub_cb: Callable[[Any], None] = sub_cb def set_value(self, value): """ Set value of the attribute. - :param value: Value. - :return: Returns True if value was applied. + + Args: + value (type): Value. + + Returns: + bool: Returns True if value was applied. """ self.value = self._correct_type(value) @@ -34,14 +47,21 @@ def set_value(self, value): if self.comm_obj.write_value_callback is not None: self.comm_obj.write_value_callback(self.value) - logging.debug(f'New value for {self.name} is {self.value}') + _logger.debug(f'New value for {self.name} is {self.value}') return True def _correct_type(self, value): """ Converts a value to the attribute type. - :param value: Value. - :return: Converted value. If conversion is not possible, returns a default value of that type. + + Args: + value (type): Value. + + Returns: + type: Converted value. If conversion is not possible, returns a default value of that type. + + Raises: + Exception: If conversion fails. """ try: converted_value = self.type(value) @@ -52,7 +72,24 @@ def _correct_type(self, value): def attach_communication_object(self, communication_object): """ Attach a communication object to the attribute, e.g. if an OPC UA node needs to be created for the attribute. - :param communication_object: Communication object. - :return: + + Args: + communication_object (type): Communication object. """ self.comm_obj = communication_object + + def attach_subscription_callback(self, sub_cb: Callable[[Any], None]): + """ + Attach a subscription callback to the attribute. + + Args: + sub_cb (Callable[[Any], None]): + Subscription callback function that will be called when the value changes. + """ + self.sub_cb = sub_cb + + def remove_subscription_callback(self): + """ + Remove the subscription callback from the attribute. + """ + self.sub_cb = None diff --git a/src/mtppy/command_codes.py b/src/mtppy/command_codes.py index 3f3964d..ac0c049 100644 --- a/src/mtppy/command_codes.py +++ b/src/mtppy/command_codes.py @@ -29,13 +29,17 @@ def __init__(self): def get_list_int(self): """ Provides a list of all command codes in int format. - :return: + + Returns: + list: List of command codes in int format. """ return list(self.int_code.keys()) def get_list_str(self): """ Provides a list of all command codes in string format. - :return: + + Returns: + list: List of command codes in string format. """ return list(self.int_code.values()) diff --git a/src/mtppy/command_en_control.py b/src/mtppy/command_en_control.py index cc6ea9c..10ca285 100644 --- a/src/mtppy/command_en_control.py +++ b/src/mtppy/command_en_control.py @@ -1,5 +1,7 @@ import logging +_logger = logging.getLogger(__name__) + class CommandEnControl: def __init__(self): @@ -19,14 +21,14 @@ def __init__(self): 'restart': {'default': False, 'value': False, 'bit_no': 9, 'int': 512}, 'complete': {'default': False, 'value': False, 'bit_no': 10, 'int': 1024}, } - self.hold_enabled = True - self.pause_enabled = True + self.hold_enabled = False + self.pause_enabled = False self.restart_enabled = True + self.__restart_enabled_actual = True def set_default(self): """ Applies default values to all command enable flags. - :return: """ for command_en in self.command_en: self.command_en[command_en]['value'] = self.command_en[command_en]['default'] @@ -34,7 +36,6 @@ def set_default(self): def disable_all(self): """ Sets False to all command enable flags. - :return: """ for command_en in self.command_en: self.command_en[command_en]['value'] = False @@ -42,8 +43,12 @@ def disable_all(self): def is_enabled(self, cmd: str): """ Checks if a given command is enabled right now. - :param cmd: Command to check. - :return: Command enable flag state. + + Args: + cmd (str): Command to check. + + Returns: + bool: Command enable flag state. """ if cmd in self.command_en.keys(): return self.command_en[cmd]['value'] @@ -53,7 +58,9 @@ def is_enabled(self, cmd: str): def get_command_en(self): """ Calculates a sum of all command enable flags. - :return: Sum of all command enable flags. + + Returns: + int: Sum of all command enable flags. """ command_en_sum = 0 for command in self.command_en.values(): @@ -64,9 +71,10 @@ def get_command_en(self): def set_command_en(self, cmd: str, value: bool): """ Sets a specific command enable to a given value. - :param cmd: Command enable flag. - :param value: Value to be set. - :return: + + Args: + cmd (str): Command enable flag. + value (bool): Value to be set. """ if cmd in self.command_en.keys(): self.command_en[cmd]['value'] = value @@ -74,37 +82,58 @@ def set_command_en(self, cmd: str, value: bool): def enable_hold_loop(self, value: bool): """ Enables hold loop. - :param value: True if the hold loop is to active and False if to disable. - :return: + + Args: + value (bool): True if the hold loop is to active and False if to disable. """ self.hold_enabled = value def enable_pause_loop(self, value: bool): """ Enables pause loop. - :param value: True if the pause loop is to active and False if to disable. - :return: + + Args: + value (bool): True if the pause loop is to active and False if to disable. """ self.pause_enabled = value def enable_restart(self, value: bool): """ Enables restart loop. - :param value: True if the restart command is to active and False if to disable. - :return: + + Args: + value (bool): True if the restart command is to active and False if to disable. """ self.restart_enabled = value + self.__restart_enabled_actual = value + + def disable_restart_temporarily(self): + """ + Disables restart command without overriding the actual setting. + """ + self.restart_enabled = False + self.set_command_en('restart', False) + + def restore_restart(self): + """ + Restores the restart command to the actual setting. + """ + self.restart_enabled = self.__restart_enabled_actual def execute(self, state: str): """ Executes command enable changes required for a certain state. - :param state: Given state. - :return: + + Args: + state (str): Given state. """ - logging.debug(f'CommandEn changed to correspond {state}') + _logger.debug(f'CommandEn changed to correspond {state}') exec(f'self._execute_{state}()') def _execute_undefined(self): + """ + Disables all command enable flags. + """ self.disable_all() def _execute_idle(self): @@ -118,8 +147,7 @@ def _execute_idle(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) def _execute_starting(self): @@ -133,8 +161,7 @@ def _execute_starting(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', True) def _execute_execute(self): @@ -163,8 +190,7 @@ def _execute_completing(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) def _execute_completed(self): @@ -178,8 +204,7 @@ def _execute_completed(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) def _execute_resuming(self): @@ -193,8 +218,7 @@ def _execute_resuming(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', True) def _execute_paused(self): @@ -208,8 +232,7 @@ def _execute_paused(self): self.set_command_en('pause', False) self.set_command_en('resume', True) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', True) def _execute_pausing(self): @@ -223,8 +246,7 @@ def _execute_pausing(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', True) def _execute_holding(self): @@ -238,8 +260,7 @@ def _execute_holding(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) def _execute_held(self): @@ -253,8 +274,7 @@ def _execute_held(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) def _execute_unholding(self): @@ -268,8 +288,7 @@ def _execute_unholding(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', True) def _execute_stopping(self): @@ -283,8 +302,7 @@ def _execute_stopping(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) def _execute_stopped(self): @@ -298,8 +316,7 @@ def _execute_stopped(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) def _execute_aborting(self): @@ -313,8 +330,7 @@ def _execute_aborting(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', False) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) def _execute_aborted(self): @@ -328,8 +344,7 @@ def _execute_aborted(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', False) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) def _execute_resetting(self): @@ -343,6 +358,5 @@ def _execute_resetting(self): self.set_command_en('pause', False) self.set_command_en('resume', False) self.set_command_en('abort', True) - if self.restart_enabled: - self.set_command_en('restart', False) + self.set_command_en('restart', False) self.set_command_en('complete', False) diff --git a/src/mtppy/communication_object.py b/src/mtppy/communication_object.py index 7010678..b7cfa6f 100644 --- a/src/mtppy/communication_object.py +++ b/src/mtppy/communication_object.py @@ -2,8 +2,10 @@ class OPCUACommunicationObject: def __init__(self, opcua_node_obj, node_id): """ Represents a communication object for OPC UA for an attribute instance. - :param opcua_node_obj: OPC UA node object. - :param node_id: OPCUA node id. + + Args: + opcua_node_obj (object): OPC UA node object. + node_id (str): OPCUA node id. """ self.opcua_node_obj = opcua_node_obj self.node_id = node_id diff --git a/src/mtppy/indicator_elements.py b/src/mtppy/indicator_elements.py index dd03292..cf86145 100644 --- a/src/mtppy/indicator_elements.py +++ b/src/mtppy/indicator_elements.py @@ -3,6 +3,8 @@ from mtppy.attribute import Attribute from mtppy.suc_data_assembly import SUCIndicatorElement +_logger = logging.getLogger(__name__) + class AnaView(SUCIndicatorElement): def __init__(self, tag_name: str, tag_description: str = '', v_scl_min: float = 0, v_scl_max: float = 100, @@ -23,7 +25,7 @@ def __init__(self, tag_name: str, tag_description: str = '', v_scl_min: float = def set_v(self, value): self.attributes['V'].set_value(value) - logging.debug('V set to %s' % value) + _logger.debug('V set to %s' % value) class BinView(SUCIndicatorElement): @@ -42,7 +44,7 @@ def __init__(self, tag_name: str, tag_description: str = '', v_state_0: str = 'f def set_v(self, value): self.attributes['V'].set_value(value) - logging.debug('V set to %s' % value) + _logger.debug('V set to %s' % value) class DIntView(SUCIndicatorElement): @@ -64,7 +66,7 @@ def __init__(self, tag_name: str, tag_description: str = '', v_scl_min: int = 0, def set_v(self, value): self.attributes['V'].set_value(value) - logging.debug('V set to %s' % value) + _logger.debug('V set to %s' % value) class StringView(SUCIndicatorElement): @@ -78,4 +80,4 @@ def __init__(self, tag_name: str, tag_description: str = ''): def set_v(self, value): self.attributes['V'].set_value(value) - logging.debug('V set to %s' % value) + _logger.debug('V set to %s' % value) diff --git a/src/mtppy/mtp_generator.py b/src/mtppy/mtp_generator.py index 5ec26ec..22bbcf0 100644 --- a/src/mtppy/mtp_generator.py +++ b/src/mtppy/mtp_generator.py @@ -8,9 +8,11 @@ class MTPGenerator: def __init__(self, writer_infos: dict, export_path: str, manifest_template_path: str): """ Represents an MTP generator. - :param writer_infos: Writer info section that will be put in the header of the generate manifest. - :param export_path: Path where to save the generated MTP manifest. - :param manifest_template_path: Path where the manifest template is placed. + + Args: + writer_infos (dict): Writer info section that will be put in the header of the generated manifest. + export_path (str): Path where to save the generated MTP manifest. + manifest_template_path (str): Path where the manifest template is placed. """ # path of a manifest template containing general infos @@ -73,10 +75,12 @@ def edit_writer_information(self): def add_module_type_package(self, version: str, name: str, description: str): """ - add internal element module type package to instance hierarchy mtp - :param version: version of mtp - :param name: name of mtp - :param description: description of mtp + Add internal element module type package to instance hierarchy MTP. + + Args: + version (str): Version of MTP. + name (str): Name of MTP. + description (str): Description of MTP. """ # create internal element @@ -106,10 +110,14 @@ def __add_communication_set(self): def create_instance(self, data_assembly: SUCDataAssembly, opc_node_id: str) -> ET.Element: """ - create instance for each data assembly - :param data_assembly: data assembly object - :param opc_node_id: opcua node id used to construct instance name - :return: manifest instance of data assembly + Create instance for each data assembly. + + Args: + data_assembly (SUCDataAssembly): Data assembly object. + opc_node_id (str): OPC UA node ID used to construct instance name. + + Returns: + ET.Element: Manifest instance of data assembly. """ instance_id = self.random_id_generator() instance_name = opc_node_id.split('=')[-1] @@ -123,13 +131,16 @@ def create_instance(self, data_assembly: SUCDataAssembly, opc_node_id: str) -> E def add_data_assembly_to_instance_list(self, instance_name: str, instance_id: str, instance_basic_type: str, instance_type_name: str) -> ET.Element: """ - add data object (subclasses of data assembly) to instance list - :param instance_name: name of data object - :param instance_id: id of data object - :param instance_basic_type: type of data assembly (service, Procedure, operation element etc.) - :param instance_type_name: used to distinguish data objects that have the same parent type (e.g. AnaSerParam, - StringSerParam, DIntSerParam, etc.) - :return: instance of data assembly + Add data object (subclasses of data assembly) to instance list. + + Args: + instance_name (str): Name of data object. + instance_id (str): ID of data object. + instance_basic_type (str): Type of data assembly (service, Procedure, operation element etc.). + instance_type_name (str): Used to distinguish data objects that have the same parent type. + + Returns: + ET.Element: Instance of data assembly. """ if instance_basic_type == 'Service': name = instance_name + '.ServiceControl' @@ -146,6 +157,9 @@ def add_data_assembly_to_instance_list(self, instance_name: str, instance_id: st elif instance_basic_type == 'SUCActiveElement': name = instance_name reference_path = 'MTPDataObjectSUCLib/DataAssembly/ActiveElement/' + instance_type_name + elif instance_basic_type == 'SUCParameterElement': + name = instance_name + reference_path = 'MTPDataObjectSUCLib/DataAssembly/ParameterElement/' + instance_type_name else: raise TypeError('data assembly type error') @@ -158,10 +172,14 @@ def add_data_assembly_to_instance_list(self, instance_name: str, instance_id: st def create_components_for_services(self, data_assembly: SUCDataAssembly, section: str) -> str: """ - create components for InstanceHierarchy Services - :param parent_elem: parent element of current component (e.g. parent of a procedure is a service) - :param data_assembly: data assembly object - :return: component of services: eg. service, procedure, config parameter, procedure parameter etc. + Create components for InstanceHierarchy Services. + + Args: + parent_elem (ET.Element): Parent element of current component (e.g. parent of a procedure is a service). + data_assembly (SUCDataAssembly): Data assembly object. + + Returns: + ET.Element: Component of services (e.g., service, procedure, config parameter, procedure parameter, etc.). """ services_component_id = self.random_id_generator() link_id = self.add_components_to_services(data_assembly, services_component_id, section) @@ -237,9 +255,11 @@ def add_components_to_services(self, data_assembly: SUCDataAssembly, sc_id: str, @staticmethod def add_linked_attr(parent: ET.Element, link_id: str): """ - add attribute whose value refers to an InternalElement under InstanceList - :param parent: parent of attribute - :param link_id: id linking to another element + Add attribute whose value refers to an InternalElement under InstanceList. + + Args: + parent (ET.Element): Parent of attribute. + link_id (str): ID linking to another element. """ attr = ET.SubElement(parent, 'Attribute') attr.set('Name', 'RefID') @@ -249,8 +269,10 @@ def add_linked_attr(parent: ET.Element, link_id: str): def __add_services_set(self, linked_id: str): """ - ServiceSet is an InternalElement which belongs to ModuleTypePackage - :param linked_id: Url or ID referring to instance hierarchy services + ServiceSet is an InternalElement which belongs to ModuleTypePackage. + + Args: + linked_id (str): Url or ID referring to instance hierarchy services. """ self.services_set = ET.SubElement(self.module_type_package, 'InternalElement') self.generate_attributes(self.services_set, 'Services', self.random_id_generator(), @@ -261,8 +283,12 @@ def __add_services_set(self, linked_id: str): def __add_service_to_service_set(self, name: str, refPath: str, linked_id: str): """ - service object in this function is an ExternalDataConnector referring to InstanceHierarchy: Services which - could be created in the same manifest or in a separate .aml file. + Service object in this function is an ExternalDataConnector referring to InstanceHierarchy: Services. + + Args: + name (str): Name of the service. + refPath (str): Reference path for the service. + linked_id (str): ID linking to InstanceHierarchy: Services. """ service = ET.SubElement(self.services_set, 'ExternalInterface') self.generate_attributes(service, name, self.random_id_generator(), refPath) @@ -271,11 +297,13 @@ def __add_service_to_service_set(self, name: str, refPath: str, linked_id: str): @staticmethod def add_attr_to_instance(parent_elem: ET.Element, name: str, default_value: str, attr_id: str): """ - add attrbutes of each data assembly to the corresponding instance - :param parent_elem: data assembly to which the attributes should be added - :param name: attribute name - :param default_value: default value of attribute - :param attr_id: each attribute has the same ID as the external interface of source list + Add attributes of each data assembly to the corresponding instance. + + Args: + parent_elem (ET.Element): Data assembly to which the attributes should be added. + name (str): Attribute name. + default_value (str): Default value of the attribute. + attr_id (str): ID of the attribute, same as the external interface of source list. """ attr = ET.SubElement(parent_elem, 'Attribute') attr.set('Name', name) @@ -294,8 +322,10 @@ def add_attr_to_instance(parent_elem: ET.Element, name: str, default_value: str, def add_opcua_server(self, endpoint: str): """ - add InternalElement opcua server to ModuleTypePackage/CommunicationSet/SourceList - :param endpoint: endpoint of opc ua server + Add InternalElement OPC UA server to ModuleTypePackage/CommunicationSet/SourceList. + + Args: + endpoint (str): Endpoint of OPC UA server. """ self.opcua_server = ET.SubElement(self.source_list, 'InternalElement') self.generate_attributes(self.opcua_server, 'OPCServer', self.random_id_generator(), @@ -308,11 +338,13 @@ def add_opcua_server(self, endpoint: str): def add_external_interface(self, opc_node_id: str, opc_ns: int, linked_attr_id: str, access=1): """ - add opc ua node als ExternalInterface to ModuleTypePackage/CommunicationSet/SourceList/OPCUAServer - :param opc_node_id: opc ua node id - :param opc_ns: opc ua namespace - :param linked_attr_id: id connecting external interface and the corresponding attribute of data assembly - :param access: access level of each external element + Add OPC UA node as ExternalInterface to ModuleTypePackage/CommunicationSet/SourceList/OPCUAServer. + + Args: + opc_node_id (str): OPC UA node ID. + opc_ns (int): OPC UA namespace. + linked_attr_id (str): ID connecting external interface and the corresponding attribute of data assembly. + access (int, optional): Access level of each external element. Defaults to 1. """ node_identifier = opc_node_id node_name = opc_node_id.split('=')[-1] @@ -356,8 +388,10 @@ def apply_add_supported_role_class(self): def add_supported_role_class(self, parent: ET.Element): """ - add SupportedRoleClass to all InternalElement - :param parent: internal element + Add SupportedRoleClass to all InternalElement. + + Args: + parent (ET.Element): Internal element. """ for internal_element in parent.findall('InternalElement'): SupportedRoleClass = ET.SubElement(internal_element, 'SupportedRoleClass') @@ -369,8 +403,10 @@ def add_supported_role_class(self, parent: ET.Element): @staticmethod def random_id_generator() -> str: """ - # generate random id for different elements - :return: id which contains five parts + Generate random ID for different elements. + + Returns: + str: ID which contains five parts. """ # generate random id for different elements id1 = ''.join(random.sample(string.ascii_letters + string.digits, 4)) @@ -393,9 +429,11 @@ def export_manifest(self): def pretty_print(self, elem: ET.Element, level=0): """ - add indents to exported .aml file - :param elem: root of the element tree - :param level: layer of the element tree, 0 represents the first layer (root) + Add indents to exported .aml file. + + Args: + elem (ET.Element): Root of the element tree. + level (int): Layer of the element tree, 0 represents the first layer (root). """ i = "\n" + level * "\t" if len(elem): diff --git a/src/mtppy/opcua_server_pea.py b/src/mtppy/opcua_server_pea.py index 883d409..2de3994 100644 --- a/src/mtppy/opcua_server_pea.py +++ b/src/mtppy/opcua_server_pea.py @@ -1,50 +1,142 @@ import logging -from opcua import Server, ua +from opcua import Server, ua, Node from mtppy.communication_object import OPCUACommunicationObject from mtppy.service import Service -from mtppy.suc_data_assembly import SUCDataAssembly, SUCActiveElement +from mtppy.suc_data_assembly import SUCDataAssembly, SUCActiveElement, SUCIndicatorElement, SUCOperationElement from mtppy.mtp_generator import MTPGenerator +_logger = logging.getLogger(__name__) + class OPCUAServerPEA: - def __init__(self, mtp_generator: MTPGenerator = None, endpoint: str ='opc.tcp://127.0.0.1:4840/'): + def __init__(self, mtp_generator: MTPGenerator = None, endpoint: str = 'opc.tcp://127.0.0.1:4840/'): """ Defines an OPC UA server for PEA. - :param mtp_generator: Instance of an MTP generator. If specified, an MTP file is generated each time - the class is instantiated. If not specified, no MTP file is going to be generated. - :param endpoint: Endpoint of the OPC UA server. If not specified, opc.tcp://0.0.0.0:4840/ is used. - """ - self.service_set = {} - self.active_elements = {} - self.endpoint = endpoint - self.opcua_server = None - self.opcua_ns = 3 + + Args: + mtp_generator (MTPGenerator): Instance of an MTP generator. + endpoint (str): Endpoint of the OPC UA server. + """ + self.service_set: dict[str, Service] = {} + self.active_elements: dict[str, SUCActiveElement] = {} + self.indicator_elements: dict[str, SUCIndicatorElement] = {} + self.operation_elements: dict[str, SUCOperationElement] = {} + self.custom_data_assembly_set: dict[str, SUCDataAssembly] = {} + self.endpoint: str = endpoint + self.opcua_server: Server = None + self.opcua_ns: int = 3 self.subscription_list = SubscriptionList() self._init_opcua_server() - self.mtp = mtp_generator + self.mtp: MTPGenerator = mtp_generator + + self._folders = ['configuration_parameters', 'procedures', 'procedure_parameters', + 'process_value_ins', 'report_values', 'process_value_outs'] + """Folders that are created in the OPC UA server if found in a data assembly + even if they are not of type SUCDataAssembly.""" + + self._leaves = ['op_src_mode', 'state_machine', 'procedure_control', 'locks'] + """Folders that are created in the OPC UA server if found in a data assembly + even if they are not of type SUCDataAssembly. + + No subfolders are created for them.""" def add_service(self, service: Service): """ Add a service to the PEA. - :param service: Service instance. - :return: + + Args: + service (Service): Service instance. """ self.service_set[service.tag_name] = service def add_active_element(self, active_element: SUCActiveElement): """ Add an active element to the PEA. - :param active_element: Active element (e.g. AnaVlv, BinVlv, etc.) - :return: + + Args: + active_element (SUCActiveElement): Active element (e.g., AnaVlv, BinVlv, etc.). """ self.active_elements[active_element.tag_name] = active_element + def add_indicator_element(self, indicator_element: SUCIndicatorElement): + """ + Add an indicator element to the PEA. + + Args: + indicator_element (SUCIndicatorElement): Indicator element. + """ + self.indicator_elements[indicator_element.tag_name] = indicator_element + + def add_operation_element(self, operation_element: SUCOperationElement): + """ + Add an operation element to the PEA. + + Args: + operation_element (SUCOperationElement): Operation element. + """ + self.operation_elements[operation_element.tag_name] = operation_element + + def add_custom_data_assembly(self, data_assembly: SUCDataAssembly, folder_name: str = ''): + """ + Add a custom data assembly to the PEA. + + Args: + data_assembly (SUCDataAssembly): Custom data assembly instance. + Has to be a class derived from SUCDataAssembly. + folder_name (str): Folder to add the data assembly to. + If name is '', the data assembly is added to the root of the Server. + """ + + if not isinstance(data_assembly, SUCDataAssembly): + raise TypeError('data_assembly must be derived from type SUCDataAssembly') + + if folder_name == '': + if data_assembly.tag_name in self.custom_data_assembly_set: + raise ValueError( + f'Custom data assembly set with name {data_assembly.tag_name} ' + f'in the root folder already exists.') + self.custom_data_assembly_set[data_assembly.tag_name] = data_assembly + return + + # create an empty data assembly as folder + if folder_name not in self.custom_data_assembly_set: + self.custom_data_assembly_set[folder_name] = SUCDataAssembly(folder_name) + + # add data assembly as attribute to the empty data assembly that is used as a folder + da_folder = self.custom_data_assembly_set[folder_name] + if hasattr(da_folder, data_assembly.tag_name): + raise ValueError( + f'Custom data assembly with name {data_assembly.tag_name} ' + f'in folder {folder_name} already exists.') + setattr(da_folder, data_assembly.tag_name, data_assembly) + + def add_folders(self, folders: list[str]): + """ + Folders that are created in the OPC UA server if found in a opcua object. + even if they are not of type SUCDataAssembly. + + Args: + folders (list[str]): List of folder names. + """ + self._folders.extend(folders) + + def add_leaves(self, leaves: list[str]): + """ + Leaves that are created in the OPC UA server if found in a opcua object. + even if they are not of type SUCDataAssembly. + + No Subfolders are created for them. + + Args: + leaves (list[str]): List of leaf names. + """ + self._leaves.extend(leaves) + def _init_opcua_server(self): """ - Initialises an OPC UA server and sets the endpoint. - :return: + Initializes an OPC UA server and sets the endpoint. """ - logging.info(f'Initialisation of OPC UA server: {self.endpoint}') + _logger.info(f'Initialisation of OPC UA server: {self.endpoint}') self.opcua_server = Server() self.opcua_server.set_endpoint(self.endpoint) # self.opcua_ns = self.opcua_server.register_namespace('namespace_idx') @@ -52,41 +144,41 @@ def _init_opcua_server(self): def get_opcua_server(self): """ Get an OPC UA server instance object. - :return: + + Returns: + Server: OPC UA server instance. """ return self.opcua_server def get_opcua_ns(self): """ Get an OPC UA server namespace index. - :return: + + Returns: + int: Namespace index. """ return self.opcua_ns def run_opcua_server(self): """ Starts the OPC UA server instance. - :return: """ self.opcua_server.start() self._build_opcua_server() self._start_subscription() def set_services_in_idle(self): + """ + Sets all services to idle state. + """ for service in self.service_set.values(): - service.init_idle_state() + service._init_idle_state() def _build_opcua_server(self): """ Creates an OPC UA server instance including required nodes according to defined data assemblies. - :return: """ - logging.info(f'Adding OPC UA nodes to the server structure according to the PEA structure:') - ns = self.opcua_ns - server = self.opcua_server.get_objects_node() - - services_node_id = f'ns={ns};s=services' - services_node = server.add_folder(services_node_id, "services") + _logger.info(f'Adding OPC UA nodes to the server structure according to the PEA structure:') # initiate a new MTP that will be added to InstanceHierarchy: ModuleTypePackage if self.mtp: @@ -96,15 +188,21 @@ def _build_opcua_server(self): if self.mtp: self.mtp.add_opcua_server(self.endpoint) - for service in self.service_set.values(): - logging.info(f'- service {service.tag_name}') - self._create_opcua_objects_for_folders(service, services_node_id, services_node) + # add service elements + if self.service_set.__len__() > 0: + self._create_opcua_element(self.service_set, "services") - act_elem_node_id = f'ns={ns};s=active_elements' - act_elem_node = server.add_folder(act_elem_node_id, "active_elements") - for active_element in self.active_elements.values(): - logging.info(f'- active element {active_element.tag_name}') - self._create_opcua_objects_for_folders(active_element, act_elem_node_id, act_elem_node) + # add active, indicator and operation elements + if self.active_elements.__len__() > 0: + self._create_opcua_element(self.active_elements, "active_elements") + if self.indicator_elements.__len__() > 0: + self._create_opcua_element(self.indicator_elements, "indicator_elements") + if self.operation_elements.__len__() > 0: + self._create_opcua_element(self.operation_elements, "operation_elements") + + # add custom data assemblies + if self.custom_data_assembly_set.__len__() > 0: + self._create_opcua_element(self.custom_data_assembly_set, "") # add SupportedRoleClass to all InternalElements if self.mtp: @@ -112,76 +210,106 @@ def _build_opcua_server(self): # export manifest.aml if self.mtp: - logging.info(f'MTP manifest export to {self.mtp.export_path}') + _logger.info(f'MTP manifest export to {self.mtp.export_path}') self.mtp.export_manifest() - def _create_opcua_objects_for_folders(self, data_assembly: SUCDataAssembly, parent_opcua_prefix: str, parent_opcua_object): + def _create_opcua_element(self, elements: dict[str, SUCDataAssembly], folder_name: str): """ - Iterates over data assemblies to create OPC UA folders. - :param data_assembly: Data assembly. - :param parent_opcua_prefix: Prefix as a string to add in front of the data assembly tag. - :param parent_opcua_object: Parent OPC UA node where the data assembly is to add to. - :return: + Create OPC UA nodes for a specific element type (active elements, indicator elements, operation elements). + + Args: + elements (dict[str, SUCDataAssembly]): Dictionary of elements. + folder_name (str): Name of the folder to create in the OPC UA server. + If folder_name is '', the elements are added to the root of the Server. + """ + ns = self.opcua_ns + root_node = self.opcua_server.get_objects_node() + element_node_id = f'ns={ns};s={folder_name}' + if folder_name != '': + element_node = root_node.add_folder(element_node_id, folder_name) + else: + element_node = root_node + _logger.info(f'- {folder_name}') + for element in elements.values(): + _logger.info(f'-- element {element.tag_name}') + self._create_opcua_objects_for_folders(element, element_node_id, element_node) + + def _create_opcua_objects_for_folders(self, data_assembly: SUCDataAssembly, + parent_opcua_prefix: str, parent_opcua_object: Node, + name: str = None, instance=None, link_id=None): """ - da_node_id = f'{parent_opcua_prefix}.{data_assembly.tag_name}' - da_node = parent_opcua_object.add_folder(da_node_id, data_assembly.tag_name) + Iterates over data assemblies to create OPC UA folders. + + Args: + data_assembly (SUCDataAssembly): Data assembly. + parent_opcua_prefix (str): Prefix to add in front of the data assembly tag. + parent_opcua_object: Parent OPC UA node where the data assembly is added. + name (str): Name of the data assembly. If None, the tag name of the data assembly is used. + instance: Instance to what the data assembly belongs. + link_id: ID to link the data assembly to. + """ + if name is None: + name = data_assembly.tag_name + da_node_id = parent_opcua_prefix + if parent_opcua_prefix != f'ns={self.opcua_ns};s=': + # node not added to root folder + da_node_id += '.' + da_node_id += name + + da_node = parent_opcua_object.add_folder(da_node_id, name) + _logger.debug(f'OPCUA Folder: {da_node_id}, Name: {name}') # type of data assembly (e.g. services, active_elements, procedures etc.) da_type = parent_opcua_prefix.split('=')[-1].split('.')[-1] - folders = ['configuration_parameters', 'procedures','procedure_parameters', - 'process_value_ins', 'report_values', 'process_value_outs'] - leaves = ['op_src_mode', 'state_machine', 'procedure_control'] - # create instance of ServiceControl, HealthStateView, DIntServParam etc. - if self.mtp: - instance = self.mtp.create_instance(data_assembly, da_node_id) - else: - instance = None + if name not in self._leaves and not isinstance(data_assembly, dict): + if self.mtp: + instance = self.mtp.create_instance(data_assembly, da_node_id) - if self.mtp: - link_id = self.mtp.random_id_generator() - if da_type == 'services' or da_type in folders: - link_id = self.mtp.create_components_for_services(data_assembly, da_type) - else: - link_id = None + if self.mtp: + link_id = self.mtp.random_id_generator() + if da_type == 'services' or da_type in self._folders: + link_id = self.mtp.create_components_for_services(data_assembly, da_type) + # Add attributes of data assembly if hasattr(data_assembly, 'attributes'): self._create_opcua_objects_for_leaves(data_assembly, da_node_id, da_node, instance) - for section_name in folders + leaves: - if not hasattr(data_assembly, section_name): + # Find any variable of data_assembly that is of type SUCDataAssembly + # and create corresponding folders and leaves for it + for variable_name, value in self.__get_objects_attributes(data_assembly).items(): + if not isinstance(value, SUCDataAssembly) and variable_name not in self._folders + self._leaves: continue - section = eval(f'data_assembly.{section_name}') - section_node_id = f'{da_node_id}.{section_name}' - section_node = da_node.add_folder(section_node_id, section_name) - if section_name in folders: - for parameter in eval(f'data_assembly.{section_name}.values()'): - self._create_opcua_objects_for_folders(parameter, section_node_id, section_node) - if section_name in leaves: - self._create_opcua_objects_for_leaves(section, section_node_id, section_node, instance) + if name in self._leaves: + continue # do not create folders for leaves + folder_name = value.tag_name if hasattr(value, 'tag_name') else variable_name + self._create_opcua_objects_for_folders( + value, da_node_id, da_node, folder_name, instance, link_id) # create linked obj between instance and service component - if self.mtp: + if self.mtp and name not in self._leaves: self.mtp.add_linked_attr(instance, link_id) - def _create_opcua_objects_for_leaves(self, opcua_object, parent_opcua_prefix: str, parent_opcua_object, par_instance): + def _create_opcua_objects_for_leaves(self, opcua_object: SUCDataAssembly, parent_opcua_prefix: str, parent_opcua_object: Node, par_instance): """ Iterates over end objects (leaves) of data assemblies to create corresponding OPC UA nodes. - :param opcua_object: Element of a data assembly that an OPC UA node is to create for. - :param parent_opcua_prefix: Prefix as a string to add in front of the data assembly tag. - :param parent_opcua_object: Parent OPC UA node where the data assembly is to add to. - :param par_instance: Parameter instance. - :return: + + Args: + opcua_object: Element of a data assembly that an OPC UA node is created for. + parent_opcua_prefix (str): Prefix to add in front of the data assembly tag. + parent_opcua_object: Parent OPC UA node where the data assembly is added. + par_instance: Parameter instance. """ for attr in opcua_object.attributes.values(): attribute_node_id = f'{parent_opcua_prefix}.{attr.name}' # We attach communication objects to be able to write values on opcua server on attributes change opcua_type = self._infer_data_type(attr.type) - opcua_node_obj = parent_opcua_object.add_variable(attribute_node_id, attr.name, attr.init_value, - varianttype=opcua_type) - logging.debug(f'OPCUA Node: {attribute_node_id}, Name: {attr.name}, Value: {attr.init_value}') + opcua_node_obj: Node = parent_opcua_object.add_variable(attribute_node_id, attr.name, attr.init_value, + varianttype=opcua_type) + _logger.debug( + f'OPCUA Node: {attribute_node_id}, Name: {attr.name}, Value: {attr.init_value}') opcua_node_obj.set_writable(False) opcua_comm_obj = OPCUACommunicationObject(opcua_node_obj, node_id=opcua_node_obj) attr.attach_communication_object(opcua_comm_obj) @@ -194,18 +322,19 @@ def _create_opcua_objects_for_leaves(self, opcua_object, parent_opcua_prefix: st linked_id = None """ - add attributes of data assembly to corresponding instance under InstanceList + add attributes of data assembly to corresponding instance under InstanceList e.g.: attributes of services belong to InstanceList/ServiceControl - exception: some attributes of procedure ('ProcedureId', 'IsSelfCompleting', 'IsDefault') should be - added to InstanceHierarchy_Service/service/procedure. The other attributes of procedure should belong to + exception: some attributes of procedure ('ProcedureId', 'IsSelfCompleting', 'IsDefault') should be + added to InstanceHierarchy_Service/service/procedure. The other attributes of procedure should belong to InstanceList/HeathStateView """ if type(opcua_object).__name__ == 'Procedure' and attr.name in ['ProcedureId', 'IsSelfCompleting', 'IsDefault']: pass else: if self.mtp: - self.mtp.add_attr_to_instance(par_instance, attr.name, attr.init_value, linked_id) + self.mtp.add_attr_to_instance( + par_instance, attr.name, attr.init_value, linked_id) # We subscribe to nodes that are writable attributes if attr.sub_cb is not None: @@ -215,9 +344,13 @@ def _create_opcua_objects_for_leaves(self, opcua_object, parent_opcua_prefix: st @staticmethod def _infer_data_type(attribute_data_type): """ - Translate a python data type to a suitable OPC UA data type. - :param attribute_data_type: Python variable. - :return: OPC UA data type + Translate a Python data type to a suitable OPC UA data type. + + Args: + attribute_data_type (type): Python variable type. + + Returns: + ua.VariantType: OPC UA data type. """ if attribute_data_type == int: return ua.VariantType.Int64 @@ -233,12 +366,32 @@ def _infer_data_type(attribute_data_type): def _start_subscription(self): """ Subscribes to defined OPC UA nodes. - :return: """ handler = Marshalling() handler.import_subscription_list(self.subscription_list) sub = self.opcua_server.create_subscription(500, handler) - handle = sub.subscribe_data_change(self.subscription_list.get_nodeid_list()) + nodeid_list = self.subscription_list.get_nodeid_list() + if nodeid_list is None: + _logger.warning('No subscriptions to OPC UA nodes defined.') + return + handle = sub.subscribe_data_change(nodeid_list) + + def __get_objects_attributes(self, object) -> dict: + """ + Get all attributes of the given object. + + Args: + object: any python object. + """ + if isinstance(object, dict): + return object + if isinstance(object, list): + # return a dictionary with index as key + return {f'{i}': item for i, item in enumerate(object)} + try: + return vars(object) + except TypeError: + return {} class SubscriptionList: @@ -250,10 +403,11 @@ def __init__(self): def append(self, node_id, cb_value_change): """ - Add an subscription entity. - :param node_id: OPC UA node. - :param cb_value_change: Callback function for a value change. - :return: + Add a subscription entity. + + Args: + node_id: OPC UA node. + cb_value_change (function): Callback function for a value change. """ identifier = node_id.nodeid.Identifier self.sub_list[identifier] = {'node_id': node_id, 'callback': cb_value_change} @@ -261,7 +415,9 @@ def append(self, node_id, cb_value_change): def get_nodeid_list(self): """ Extract a list of node ids in the subscription list. - :return: List of node ids. + + Returns: + list: List of node ids. """ if len(self.sub_list) == 0: return None @@ -274,8 +430,12 @@ def get_nodeid_list(self): def get_callback(self, node_id): """ Get a callback function for a specific OPC UA node. - :param node_id: OPC UA node id - :return: + + Args: + node_id: OPC UA node id. + + Returns: + function: Callback function. """ identifier = node_id.nodeid.Identifier if identifier in self.sub_list.keys(): @@ -294,18 +454,20 @@ def __init__(self): def import_subscription_list(self, subscription_list: SubscriptionList): """ Import a subscription list. - :param subscription_list: Subscription list. - :return: + + Args: + subscription_list (SubscriptionList): Subscription list. """ self.subscription_list = subscription_list def datachange_notification(self, node, val, data): """ Executes a callback function if data value changes. - :param node: OPC UA node. - :param val: Value after change. - :param data: Not used. - :return: + + Args: + node: OPC UA node. + val: Value after change. + data: Not used. """ callback = self.find_set_callback(node) if callback is not None: @@ -313,12 +475,16 @@ def datachange_notification(self, node, val, data): callback(val) except Exception as exc: pass - logging.warning(f'Something wrong with callback {callback}: {exc}') + _logger.warning(f'Something wrong with callback {callback}: {exc}') def find_set_callback(self, node_id): """ Finds a callback function to a specific OPC UA node by nodeid. - :param node_id: Node id. - :return: Callback function. + + Args: + node_id: Node id. + + Returns: + function: Callback function. """ return self.subscription_list.get_callback(node_id) diff --git a/src/mtppy/operation_elements.py b/src/mtppy/operation_elements.py index 364846d..804cd5c 100644 --- a/src/mtppy/operation_elements.py +++ b/src/mtppy/operation_elements.py @@ -1,19 +1,24 @@ import logging from mtppy.attribute import Attribute -from mtppy.operation_source_mode import OperationSourceMode +from mtppy.operation_source_mode import SourceMode from mtppy.suc_data_assembly import SUCOperationElement -class AnaServParam(SUCOperationElement): - def __init__(self, tag_name: str, tag_description: str = '', v_min: float = 0, v_max: float = 100, - v_scl_min: float = 0, v_scl_max: float = 100, v_unit: int = 0): - """ - Analog Service Parameter (AnaServParam). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. - """ - super().__init__(tag_name, tag_description) +_logger = logging.getLogger(__name__) + + +class AnaMan(SUCOperationElement): + """ + Analog Operation Element (AnaMan). + Table 32 from VDI/VDE/NAMUR 2658-3. + """ - self.op_src_mode = OperationSourceMode() + def __init__(self, tag_name: str, tag_description: str = '', + v_min: float = 0.0, v_max: float = 100.0, + v_scl_min: float = 0.0, v_scl_max: float = 100.0, + v_unit: int = 0, init_value: float = 0.0): + super().__init__(tag_name, tag_description) self.v_min = v_min self.v_max = v_max @@ -21,238 +26,234 @@ def __init__(self, tag_name: str, tag_description: str = '', v_min: float = 0, v self.v_scl_max = v_scl_max self.v_unit = v_unit - self._add_attribute(Attribute('VOp', float, init_value=v_min, sub_cb=self.set_v_op)) - self._add_attribute(Attribute('VInt', float, init_value=v_min, sub_cb=self.set_v_int)) - self._add_attribute(Attribute('VExt', float, init_value=v_min, sub_cb=self.set_v_ext)) - self._add_attribute(Attribute('VReq', float, init_value=v_min)) - self._add_attribute(Attribute('VOut', float, init_value=0)) - self._add_attribute(Attribute('VFbk', float, init_value=0)) - self._add_attribute(Attribute('VUnit', int, init_value=self.v_unit)) - self._add_attribute(Attribute('VSclMin', float, init_value=self.v_scl_min)) - self._add_attribute(Attribute('VSclMax', float, init_value=self.v_scl_max)) - self._add_attribute(Attribute('VMin', float, init_value=self.v_min)) - self._add_attribute(Attribute('VMax', float, init_value=self.v_max)) - self._add_attribute(Attribute('Sync', bool, False)) - - def set_v_op(self, value): - logging.debug('VOp set to %s' % value) - if self.op_src_mode.attributes['StateOpAct']: - self.set_v_req(value) - - def set_v_int(self, value): - logging.debug('VInt set to %s' % value) - if self.op_src_mode.attributes['StateAutAct'] and self.op_src_mode.attributes['SrcIntAct']: - self.set_v_req(value) - - def set_v_ext(self, value): - logging.debug('VExt set to %s' % value) - if self.op_src_mode.attributes['StateAutAct'] and self.op_src_mode.attributes['SrcExtAct']: - self.set_v_req(value) - - def valid_value(self, value): - if value < self.v_min or value > self.v_max: - return False - else: - return True - - def set_v_req(self, value): + # Attributes with callbacks + self._add_attribute(Attribute('VOut', float, init_value=init_value)) + self._add_attribute(Attribute('VSclMin', float, init_value=v_scl_min)) + self._add_attribute(Attribute('VSclMax', float, init_value=v_scl_max)) + self._add_attribute(Attribute('VUnit', int, init_value=v_unit)) + self._add_attribute( + Attribute('VMan', float, init_value=init_value, sub_cb=self._set_v_man)) + self._add_attribute(Attribute('VMin', float, init_value=v_min)) + self._add_attribute(Attribute('VMax', float, init_value=v_max)) + self._add_attribute(Attribute('VRbk', float, init_value=init_value)) + self._add_attribute(Attribute('VFbk', float, init_value=init_value)) + + def valid_value(self, value: float) -> bool: + return self.v_min <= value <= self.v_max + + def _set_v_man(self, value: float): + _logger.debug(f"VMan set to {value}") if self.valid_value(value): - self.attributes['VReq'].set_value(value) - logging.debug('VReq set to %s' % value) + self.set_v_out(value) else: - logging.debug('VReq cannot be set to %s (out of range)' % value) + _logger.warning(f"VMan {value} out of range ({self.v_min} - {self.v_max})") - def set_v_out(self): - v_req = self.attributes['VReq'].value - self.attributes['VOut'].set_value(v_req) - self.set_v_fbk(v_req) - logging.debug('VOut set to %s' % v_req) + def set_v_out(self, value: float): + self.attributes['VOut'].set_value(value) + self.set_v_rbk(value) + self.set_v_fbk(value) + _logger.debug(f"VOut set to {value}") - def get_v_out(self): - return self.attributes['VOut'].value + def set_v_rbk(self, value: float): + self.attributes['VRbk'].set_value(value) + _logger.debug(f"VRbk set to {value}") - def set_v_fbk(self, value): + def set_v_fbk(self, value: float): self.attributes['VFbk'].set_value(value) - logging.debug('VFbk set to %s' % value) - - -class BinServParam(SUCOperationElement): - def __init__(self, tag_name: str, tag_description: str = '', v_state_0: str = 'false', v_state_1: str = 'true'): - """ - Binary Service Parameter (BinServParam). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. - """ - super().__init__(tag_name, tag_description) - - self.op_src_mode = OperationSourceMode() + _logger.debug(f"VFbk set to {value}") - self.v_state_0 = v_state_0 - self.v_state_1 = v_state_1 - - self._add_attribute(Attribute('VOp', bool, init_value=False, sub_cb=self.set_v_op)) - self._add_attribute(Attribute('VInt', bool, init_value=False, sub_cb=self.set_v_int)) - self._add_attribute(Attribute('VExt', bool, init_value=False, sub_cb=self.set_v_ext)) - self._add_attribute(Attribute('VReq', bool, init_value=False)) - self._add_attribute(Attribute('VOut', bool, init_value=False)) - self._add_attribute(Attribute('VFbk', bool, init_value=False)) - self._add_attribute(Attribute('VState0', str, init_value=self.v_state_0)) - self._add_attribute(Attribute('VState1', str, init_value=self.v_state_1)) - self._add_attribute(Attribute('Sync', bool, False)) - - def set_v_op(self, value): - logging.debug('VOp set to %s' % value) - if self.op_src_mode.attributes['StateOpAct']: - self.set_v_req(value) - - def set_v_int(self, value): - logging.debug('VInt set to %s' % value) - if self.op_src_mode.attributes['StateAutAct'] and self.op_src_mode.attributes['SrcIntAct']: - self.set_v_req(value) - - def set_v_ext(self, value): - logging.debug('VExt set to %s' % value) - if self.op_src_mode.attributes['StateAutAct'] and self.op_src_mode.attributes['SrcExtAct']: - self.set_v_req(value) - - def set_v_req(self, value): - self.attributes['VReq'].set_value(value) - logging.debug('VReq set to %s' % value) - - def set_v_out(self): - v_req = self.attributes['VReq'].value - self.attributes['VOut'].set_value(v_req) - self.set_v_fbk(v_req) - logging.debug('VOut set to %s' % v_req) - - def get_v_out(self): + def get_v_out(self) -> float: return self.attributes['VOut'].value - def set_v_fbk(self, value): - self.attributes['VFbk'].set_value(value) - logging.debug('VFbk set to %s' % value) - -class DIntServParam(SUCOperationElement): - def __init__(self, tag_name: str, tag_description: str = '', v_min: int = 0, v_max: int = 100, v_scl_min: int = 0, - v_scl_max: int = 100, v_unit: int = 0): - """ - Discrete Integer Service Parameter (DIntServParam). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. - """ +class AnaManInt(AnaMan): + """ + Analog Operation Element with Internal Setpoint and SourceMode (AnaManInt). + Table 33 from VDI/VDE/NAMUR 2658-3. + """ + + def __init__(self, tag_name: str, tag_description: str = '', + v_min: float = 0.0, v_max: float = 100.0, + v_scl_min: float = 0.0, v_scl_max: float = 100.0, + v_unit: int = 0, init_value: float = 0.0): + super().__init__(tag_name, tag_description, + v_min=v_min, v_max=v_max, + v_scl_min=v_scl_min, v_scl_max=v_scl_max, + v_unit=v_unit, init_value=init_value) + + self.op_src_mode = SourceMode() + + # Extensions (Table 33) + self._add_attribute(Attribute('WQC', int, init_value=0)) + self._add_attribute(Attribute('VInt', float, init_value=init_value, sub_cb=self.set_v_int)) + + def _set_v_man(self, value: float): + _logger.debug(f"VMan set to {value}") + if self.op_src_mode.attributes['SrcManAct'].value and self.valid_value(value): + self.set_v_out(value) + + def set_v_int(self, value: float): + _logger.debug(f"VInt set to {value}") + if self.op_src_mode.attributes['SrcIntAct'].value and self.valid_value(value): + self.set_v_out(value) + + +class DIntMan(SUCOperationElement): + """ + Discrete Integer Operation Element (DIntMan). + Table 34 from VDI/VDE/NAMUR 2658-3. + """ + + def __init__(self, tag_name: str, tag_description: str = '', + v_min: int = 0, v_max: int = 100, + v_scl_min: int = 0, v_scl_max: int = 100, + v_unit: int = 0, init_value: int = 0): super().__init__(tag_name, tag_description) - self.op_src_mode = OperationSourceMode() - self.v_min = v_min self.v_max = v_max self.v_scl_min = v_scl_min self.v_scl_max = v_scl_max self.v_unit = v_unit - self._add_attribute(Attribute('VOp', int, init_value=v_min, sub_cb=self.set_v_op)) - self._add_attribute(Attribute('VInt', int, init_value=v_min, sub_cb=self.set_v_int)) - self._add_attribute(Attribute('VExt', int, init_value=v_min, sub_cb=self.set_v_ext)) - self._add_attribute(Attribute('VReq', int, init_value=v_min)) - self._add_attribute(Attribute('VOut', int, init_value=0)) - self._add_attribute(Attribute('VFbk', int, init_value=0)) - self._add_attribute(Attribute('VUnit', int, init_value=self.v_unit)) - self._add_attribute(Attribute('VSclMin', int, init_value=self.v_scl_min)) - self._add_attribute(Attribute('VSclMax', int, init_value=self.v_scl_max)) - self._add_attribute(Attribute('VMin', int, init_value=self.v_min)) - self._add_attribute(Attribute('VMax', int, init_value=self.v_max)) - self._add_attribute(Attribute('Sync', bool, False)) - - def set_v_op(self, value): - logging.debug('VOp set to %s' % value) - if self.op_src_mode.attributes['StateOpAct']: - self.set_v_req(value) - - def set_v_int(self, value): - logging.debug('VInt set to %s' % value) - if self.op_src_mode.attributes['StateAutAct'] and self.op_src_mode.attributes['SrcIntAct']: - self.set_v_req(value) - - def set_v_ext(self, value): - logging.debug('VExt set to %s' % value) - if self.op_src_mode.attributes['StateAutAct'] and self.op_src_mode.attributes['SrcExtAct']: - self.set_v_req(value) - - def valid_value(self, value): - if value < self.v_min or value > self.v_max: - return False - else: - return True - - def set_v_req(self, value): + self._add_attribute(Attribute('VOut', int, init_value=init_value)) + self._add_attribute(Attribute('VSclMin', int, init_value=v_scl_min)) + self._add_attribute(Attribute('VSclMax', int, init_value=v_scl_max)) + self._add_attribute(Attribute('VUnit', int, init_value=v_unit)) + self._add_attribute(Attribute('VMan', int, init_value=init_value, sub_cb=self._set_v_man)) + self._add_attribute(Attribute('VMin', int, init_value=v_min)) + self._add_attribute(Attribute('VMax', int, init_value=v_max)) + self._add_attribute(Attribute('VRbk', int, init_value=init_value)) + self._add_attribute(Attribute('VFbk', int, init_value=init_value)) + + def valid_value(self, value: int) -> bool: + return self.v_min <= value <= self.v_max + + def _set_v_man(self, value: int): + _logger.debug(f"VMan set to {value}") if self.valid_value(value): - self.attributes['VReq'].set_value(value) - logging.debug('VReq set to %s' % value) + self.set_v_out(value) else: - logging.debug('VReq cannot be set to %s (out of range)' % value) + _logger.warning(f"VMan {value} out of range ({self.v_min} - {self.v_max})") - def set_v_out(self): - v_req = self.attributes['VReq'].value - self.attributes['VOut'].set_value(v_req) - self.set_v_fbk(v_req) - logging.debug('VOut set to %s' % v_req) + def set_v_out(self, value: int): + self.attributes['VOut'].set_value(value) + self.set_v_rbk(value) + self.set_v_fbk(value) + _logger.debug(f"VOut set to {value}") - def get_v_out(self): - return self.attributes['VOut'].value + def set_v_rbk(self, value: int): + self.attributes['VRbk'].set_value(value) + _logger.debug(f"VRbk set to {value}") - def set_v_fbk(self, value): + def set_v_fbk(self, value: int): self.attributes['VFbk'].set_value(value) - logging.debug('VFbk set to %s' % value) + _logger.debug(f"VFbk set to {value}") + + def get_v_out(self) -> int: + return self.attributes['VOut'].value -class StringServParam(SUCOperationElement): - def __init__(self, tag_name: str, tag_description: str = ''): - """ - String Service Parameter (StringServParam). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. - """ - super().__init__(tag_name, tag_description) +class DIntManInt(DIntMan): + """ + Integer Operation Element with Internal Setpoint and SourceMode (DIntManInt). + Table 35 from VDI/VDE/NAMUR 2658-3. + """ - self.op_src_mode = OperationSourceMode() + def __init__(self, tag_name: str, tag_description: str = '', + v_min: int = 0, v_max: int = 100, + v_scl_min: int = 0, v_scl_max: int = 100, + v_unit: int = 0, init_value: int = 0): + super().__init__(tag_name, tag_description, + v_min=v_min, v_max=v_max, + v_scl_min=v_scl_min, v_scl_max=v_scl_max, + v_unit=v_unit, init_value=init_value) - self._add_attribute(Attribute('VOp', str, init_value='', sub_cb=self.set_v_op)) - self._add_attribute(Attribute('VInt', str, init_value='', sub_cb=self.set_v_int)) - self._add_attribute(Attribute('VExt', str, init_value='', sub_cb=self.set_v_ext)) - self._add_attribute(Attribute('VReq', str, init_value='')) - self._add_attribute(Attribute('VOut', str, init_value='')) - self._add_attribute(Attribute('VFbk', str, init_value='')) - self._add_attribute(Attribute('Sync', bool, False)) + self.op_src_mode = SourceMode() - def set_v_op(self, value): - logging.debug('VOp set to %s' % value) - if self.op_src_mode.attributes['StateOpAct']: - self.set_v_req(value) + # Extensions (Table 35) + self._add_attribute(Attribute('WQC', int, init_value=0)) + self._add_attribute(Attribute('VInt', int, init_value=init_value, sub_cb=self.set_v_int)) - def set_v_int(self, value): - logging.debug('VInt set to %s' % value) - if self.op_src_mode.attributes['StateAutAct'] and self.op_src_mode.attributes['SrcIntAct']: - self.set_v_req(value) + def _set_v_man(self, value: int): + _logger.debug(f"VMan set to {value}") + if self.op_src_mode.attributes['SrcManAct'].value and self.valid_value(value): + self.set_v_out(value) - def set_v_ext(self, value): - logging.debug('VExt set to %s' % value) - if self.op_src_mode.attributes['StateAutAct'] and self.op_src_mode.attributes['SrcExtAct']: - self.set_v_req(value) + def set_v_int(self, value: int): + _logger.debug(f"VInt set to {value}") + if self.op_src_mode.attributes['SrcIntAct'].value and self.valid_value(value): + self.set_v_out(value) - def valid_value(self, value): - return True - def set_v_req(self, value): - if self.valid_value(value): - self.attributes['VReq'].set_value(value) - logging.debug('VReq set to %s' % value) - else: - logging.debug('VReq cannot be set to %s (out of range)' % value) +class BinMan(SUCOperationElement): + """ + Binary Operation Element (BinMan). + Table 36 from VDI/VDE/NAMUR 2658-3. + """ - def set_v_out(self): - v_req = self.attributes['VReq'].value - self.attributes['VOut'].set_value(v_req) - self.set_v_fbk(v_req) - logging.debug('VOut set to %s' % v_req) + def __init__(self, tag_name: str, tag_description: str = '', + v_state_0: str = 'Off', v_state_1: str = 'On', + init_value: bool = False): + super().__init__(tag_name, tag_description) - def get_v_out(self): - return self.attributes['VOut'].value + self.v_state_0 = v_state_0 + self.v_state_1 = v_state_1 + + self._add_attribute(Attribute('VOut', bool, init_value=init_value)) + self._add_attribute(Attribute('VState0', str, init_value=v_state_0)) + self._add_attribute(Attribute('VState1', str, init_value=v_state_1)) + self._add_attribute(Attribute('VMan', bool, init_value=init_value, sub_cb=self._set_v_man)) + self._add_attribute(Attribute('VRbk', bool, init_value=init_value)) + self._add_attribute(Attribute('VFbk', bool, init_value=init_value)) + + def _set_v_man(self, value: bool): + _logger.debug(f"VMan set to {value}") + self.set_v_out(value) + + def set_v_out(self, value: bool): + self.attributes['VOut'].set_value(value) + self.set_v_rbk(value) + self.set_v_fbk(value) + _logger.debug(f"VOut set to {value}") - def set_v_fbk(self, value): + def set_v_rbk(self, value: bool): + self.attributes['VRbk'].set_value(value) + _logger.debug(f"VRbk set to {value}") + + def set_v_fbk(self, value: bool): self.attributes['VFbk'].set_value(value) - logging.debug('VFbk set to %s' % value) + _logger.debug(f"VFbk set to {value}") + + def get_v_out(self) -> bool: + return self.attributes['VOut'].value + + +class BinManInt(BinMan): + """ + Binary Operation Element with Internal Setpoint and SourceMode (BinManInt). + Table 37 from VDI/VDE/NAMUR 2658-3. + """ + + def __init__(self, tag_name: str, tag_description: str = '', + v_state_0: str = 'Off', v_state_1: str = 'On', + init_value: bool = False): + super().__init__(tag_name, tag_description, + v_state_0=v_state_0, v_state_1=v_state_1, + init_value=init_value) + + self.op_src_mode = SourceMode() + + # Extensions (Table 37) + self._add_attribute(Attribute('WQC', int, init_value=0)) + self._add_attribute(Attribute('VInt', bool, init_value=init_value, sub_cb=self.set_v_int)) + + def _set_v_man(self, value: bool): + _logger.debug(f"VMan set to {value}") + if self.op_src_mode.attributes['SrcManAct'].value: + self.set_v_out(value) + + def set_v_int(self, value: bool): + _logger.debug(f"VInt set to {value}") + if self.op_src_mode.attributes['SrcIntAct'].value: + self.set_v_out(value) diff --git a/src/mtppy/operation_source_mode.py b/src/mtppy/operation_source_mode.py index d5c0b69..fc35b93 100644 --- a/src/mtppy/operation_source_mode.py +++ b/src/mtppy/operation_source_mode.py @@ -2,9 +2,11 @@ from mtppy.attribute import Attribute +_logger = logging.getLogger(__name__) + class OperationSourceMode: - def __init__(self): + def __init__(self, name_of_parent: str = ''): """ Represents the operation and source modes control. """ @@ -28,7 +30,7 @@ def __init__(self): 'SrcIntAct': Attribute('SrcIntAct', bool, init_value=False), 'SrcExtAct': Attribute('SrcExtAct', bool, init_value=False) } - self.switch_to_offline_mode_allowed = False + self.switch_to_offline_mode_allowed = True self.enter_offline_callbacks = [] self.exit_offline_callbacks = [] @@ -39,6 +41,9 @@ def __init__(self): self.enter_automatic_callbacks = [] self.exit_automatic_callbacks = [] + self.linked_op_src_modes = [] + self._name_of_parent = f"{name_of_parent}: " if name_of_parent != '' else '' + def allow_switch_to_offline_mode(self, allow_flag: bool): self.switch_to_offline_mode_allowed = allow_flag @@ -62,32 +67,32 @@ def add_exit_automatic_callback(self, callback: callable): def _enter_off(self): if len(self.enter_offline_callbacks): - logging.debug('Applying enter offline mode callbacks') + _logger.debug(f'{self._name_of_parent}Applying enter offline mode callbacks') [cb() for cb in self.enter_offline_callbacks] def _exit_off(self): if len(self.exit_offline_callbacks): - logging.debug('Applying exit offline mode callbacks') + _logger.debug(f'{self._name_of_parent}Applying exit offline mode callbacks') [cb() for cb in self.exit_offline_callbacks] def _enter_op(self): if len(self.enter_operator_callbacks): - logging.debug('Applying enter operator mode callbacks') + _logger.debug(f'{self._name_of_parent}Applying enter operator mode callbacks') [cb() for cb in self.enter_operator_callbacks] def _exit_op(self): if len(self.exit_operator_callbacks): - logging.debug('Applying exit operator mode callbacks') + _logger.debug(f'{self._name_of_parent}Applying exit operator mode callbacks') [cb() for cb in self.exit_operator_callbacks] def _enter_aut(self): if len(self.enter_automatic_callbacks): - logging.debug('Applying enter automatic mode callbacks') + _logger.debug(f'{self._name_of_parent}Applying enter automatic mode callbacks') [cb() for cb in self.enter_automatic_callbacks] def _exit_aut(self): if len(self.exit_automatic_callbacks): - logging.debug('Applying exit automatic mode callbacks') + _logger.debug(f'{self._name_of_parent}Applying exit automatic mode callbacks') [cb() for cb in self.exit_automatic_callbacks] def _opmode_to_off(self): @@ -105,7 +110,7 @@ def _opmode_to_off(self): self._exit_aut() self._enter_off() - logging.debug('Operation mode is now off') + _logger.debug(f'{self._name_of_parent}Operation mode is now off') self._src_to_off() def _opmode_to_aut(self): @@ -123,7 +128,7 @@ def _opmode_to_aut(self): self._exit_op() self._enter_aut() - logging.debug('Operation mode is now aut') + _logger.debug(f'{self._name_of_parent}Operation mode is now aut') self._src_to_int() def _opmode_to_op(self): @@ -140,144 +145,317 @@ def _opmode_to_op(self): self._exit_aut() self._enter_op() - logging.debug('Operation mode is now op') + _logger.debug(f'{self._name_of_parent}Operation mode is now op') self._src_to_off() + def add_linked_op_src_mode(self, linked_op_src_mode): + """ + Adds a linked operation source mode. + + Args: + linked_op_src_mode (OperationSourceMode): The linked operation source mode to add. + """ + if isinstance(linked_op_src_mode, OperationSourceMode): + self.linked_op_src_modes.append(linked_op_src_mode) + else: + raise TypeError("linked_op_src_mode must be an instance of OperationSourceMode") + + def _update_linked_op_src_modes(self, attribute_name: str, value: bool): + """ + Updates the linked operation source modes based on the attribute change. + + Args: + attribute_name (str): The name of the attribute that changed. + value (bool): The new value of the attribute. + """ + if self.linked_op_src_modes == []: + return + _logger.debug( + f'{self._name_of_parent}Updating linked op_src_modes for attribute {attribute_name} to {value}') + linked_op_src_mode: OperationSourceMode + for linked_op_src_mode in self.linked_op_src_modes: + linked_op_src_mode.attributes[attribute_name].set_value(value) + def set_state_channel(self, value: bool): - logging.debug('Operation mode channel is now %s' % value) + _logger.debug(f'{self._name_of_parent}Operation mode channel is now %s' % value) + if self.attributes['StateChannel'].value == value: + return + self.attributes['StateChannel'].set_value(value) + self._update_linked_op_src_modes('StateChannel', value) def set_state_aut_aut(self, value: bool): - logging.debug(f'StateAutAut set to {value}') + _logger.debug(f'{self._name_of_parent}StateAutAut set to {value}') if self.attributes['StateChannel'].value and value: - if self.attributes['StateOffAct'] or self.attributes['StateOpAct']: + if self.attributes['StateOffAct'].value or self.attributes['StateOpAct'].value: self._opmode_to_aut() + self._update_linked_op_src_modes('StateAutAut', value) def set_state_aut_op(self, value: bool): - logging.debug(f'StateAutOp set to {value}') + _logger.debug(f'{self._name_of_parent}StateAutOp set to {value}') if not self.attributes['StateChannel'].value and value: - if self.attributes['StateOffAct'] or self.attributes['StateOpAct']: + if self.attributes['StateOffAct'].value or self.attributes['StateOpAct'].value: self._opmode_to_aut() - self.attributes['StateAutOp'].set_value(False) + if value: + self.attributes['StateAutOp'].set_value(False) + self._update_linked_op_src_modes('StateAutOp', value) def set_state_off_aut(self, value: bool): - logging.debug(f'StateOffAut set to {value}') + _logger.debug(f'{self._name_of_parent}StateOffAut set to {value}') if self.attributes['StateChannel'].value and value and self.switch_to_offline_mode_allowed: - if self.attributes['StateAutAct'] or self.attributes['StateOpAct']: + if self.attributes['StateAutAct'].value or self.attributes['StateOpAct'].value: self._opmode_to_off() + self._update_linked_op_src_modes('StateOffAut', value) def set_state_off_op(self, value: bool): - logging.debug(f'StateOffOp set to {value}') + _logger.debug(f'{self._name_of_parent}StateOffOp set to {value}') if not self.attributes['StateChannel'].value and value and self.switch_to_offline_mode_allowed: - if self.attributes['StateAutAct'] or self.attributes['StateOpAct']: + if self.attributes['StateAutAct'].value or self.attributes['StateOpAct'].value: self._opmode_to_off() - self.attributes['StateOffOp'].set_value(False) + if value: + self.attributes['StateOffOp'].set_value(False) + self._update_linked_op_src_modes('StateOffOp', value) def set_state_op_aut(self, value: bool): - logging.debug(f'StateOpAut set to {value}') + _logger.debug(f'{self._name_of_parent}StateOpAut set to {value}') if self.attributes['StateChannel'].value and value: - if self.attributes['StateOffAct'] or self.attributes['StateOpAct']: + if self.attributes['StateOffAct'].value or self.attributes['StateOpAct'].value: self._opmode_to_op() + self._update_linked_op_src_modes('StateOpAut', value) def set_state_op_op(self, value: bool): - logging.debug(f'StateOpOp set to {value}') + _logger.debug(f'{self._name_of_parent}StateOpOp set to {value}') if not self.attributes['StateChannel'].value and value: - if self.attributes['StateOffAct'] or self.attributes['StateAutAct']: + if self.attributes['StateOffAct'].value or self.attributes['StateAutAct'].value: self._opmode_to_op() - self.attributes['StateOpOp'].set_value(False) + if value: + self.attributes['StateOpOp'].set_value(False) + self._update_linked_op_src_modes('StateOpOp', value) def _src_to_off(self): self.attributes['SrcIntAct'].set_value(False) self.attributes['SrcExtAct'].set_value(False) - logging.debug('Source mode is now off') + _logger.debug(f'{self._name_of_parent}Source mode is now off') def _src_to_int(self): self.attributes['SrcIntAct'].set_value(True) self.attributes['SrcExtAct'].set_value(False) - logging.debug('Source mode is now int') + _logger.debug(f'{self._name_of_parent}Source mode is now int') def _src_to_ext(self): self.attributes['SrcIntAct'].set_value(False) self.attributes['SrcExtAct'].set_value(True) - logging.debug('Source mode is now ext') + _logger.debug(f'{self._name_of_parent}Source mode is now ext') def set_src_channel(self, value: bool): - logging.debug('Source mode channel is now %s' % value) + _logger.debug(f'{self._name_of_parent}Source mode channel is now %s' % value) + if self.attributes['SrcChannel'].value == value: + return + self.attributes['SrcChannel'].set_value(value) + self._update_linked_op_src_modes('SrcChannel', value) def set_src_ext_aut(self, value: bool): if not self.attributes['StateOffAct'].value and value: if self.attributes['SrcChannel'].value: self._src_to_ext() + self._update_linked_op_src_modes('SrcExtAut', value) def set_src_ext_op(self, value: bool): if not self.attributes['StateOffAct'].value and value: if not self.attributes['SrcChannel'].value: self._src_to_ext() - self.attributes['SrcExtOp'].set_value(False) + if value: + self.attributes['SrcExtOp'].set_value(False) + self._update_linked_op_src_modes('SrcExtOp', value) def set_src_int_aut(self, value: bool): if not self.attributes['StateOffAct'].value and value: if self.attributes['SrcChannel'].value: self._src_to_int() + self._update_linked_op_src_modes('SrcIntAut', value) def set_src_int_op(self, value: bool): if not self.attributes['StateOffAct'].value and value: if not self.attributes['SrcChannel'].value: self._src_to_int() - self.attributes['SrcIntOp'].set_value(False) + if value: + self.attributes['SrcIntOp'].set_value(False) + self._update_linked_op_src_modes('SrcIntOp', value) -class OperationSourceModeActiveElements(OperationSourceMode): - def __init__(self): +class OperationMode(): + def __init__(self, name_of_parent: str = ''): """ - Represents the operation and source model control for active elements. + Represents the operation mode control. Used by active elements. + (Table 15 VDI/VDE/NAMUR 2658-3). """ - super().__init__() - self.attributes = { - 'StateChannel': Attribute('StateChannel', bool, init_value=False, sub_cb=self.set_state_channel), + if not hasattr(self, 'attributes'): + self.attributes: dict['str', Attribute] = {} + self.attributes.update({ + # --- Operation mode control --- + 'StateChannel': Attribute('StateChannel', bool, init_value=True, sub_cb=self.set_state_channel), 'StateOffAut': Attribute('StateOffAut', bool, init_value=False, sub_cb=self.set_state_off_aut), 'StateOpAut': Attribute('StateOpAut', bool, init_value=False, sub_cb=self.set_state_op_aut), 'StateAutAut': Attribute('StateAutAut', bool, init_value=False, sub_cb=self.set_state_aut_aut), 'StateOffOp': Attribute('StateOffOp', bool, init_value=False, sub_cb=self.set_state_off_op), 'StateOpOp': Attribute('StateOpOp', bool, init_value=False, sub_cb=self.set_state_op_op), 'StateAutOp': Attribute('StateAutOp', bool, init_value=False, sub_cb=self.set_state_aut_op), + 'StateOpAct': Attribute('StateOpAct', bool, init_value=False), - 'StateAutAct': Attribute('StateAutAct', bool, init_value=False), - 'StateOffAct': Attribute('StateOffAct', bool, init_value=True), + 'StateAutAct': Attribute('StateAutAct', bool, init_value=True), + 'StateOffAct': Attribute('StateOffAct', bool, init_value=False) + }) - 'SrcChannel': Attribute('SrcChannel', bool, init_value=False, sub_cb=self.set_src_channel), + self._name_of_parent = f"{name_of_parent}: " if name_of_parent != '' else '' + + # --- Operation mode transitions --- + def _opmode_to_off(self): + self.attributes['StateOpAct'].set_value(False) + self.attributes['StateAutAct'].set_value(False) + self.attributes['StateOffAct'].set_value(True) + _logger.debug(f'{self._name_of_parent}Operation mode is now off') + + def _opmode_to_aut(self): + self.attributes['StateOpAct'].set_value(False) + self.attributes['StateAutAct'].set_value(True) + self.attributes['StateOffAct'].set_value(False) + _logger.debug(f'{self._name_of_parent}Operation mode is now aut') + + def _opmode_to_op(self): + self.attributes['StateOpAct'].set_value(True) + self.attributes['StateAutAct'].set_value(False) + self.attributes['StateOffAct'].set_value(False) + _logger.debug(f'{self._name_of_parent}Operation mode is now op') + + # --- Callbacks for operation control --- + def set_state_channel(self, value: bool): + _logger.debug(f'{self._name_of_parent}Operation mode channel is now %s' % value) + if self.attributes['StateChannel'].value != value: + self.attributes['StateChannel'].set_value(value) + + def set_state_off_aut(self, value: bool): + _logger.debug(f'{self._name_of_parent}StateOffAut set to {value}') + if self.attributes['StateChannel'].value and value: + if self.attributes['StateOpAct'].value or self.attributes['StateAutAct'].value: + self._opmode_to_off() + + def set_state_op_aut(self, value: bool): + _logger.debug(f'{self._name_of_parent}StateOpAut set to {value}') + if self.attributes['StateChannel'].value and value: + if self.attributes['StateOffAct'].value or self.attributes['StateAutAct'].value: + self._opmode_to_op() + + def set_state_aut_aut(self, value: bool): + _logger.debug(f'{self._name_of_parent}StateAutAut set to {value}') + if self.attributes['StateChannel'].value and value: + if self.attributes['StateOffAct'].value or self.attributes['StateOpAct'].value: + self._opmode_to_aut() + + def set_state_off_op(self, value: bool): + _logger.debug(f'{self._name_of_parent}StateOffOp set to {value}') + if not self.attributes['StateChannel'].value and value: + if self.attributes['StateOpAct'].value or self.attributes['StateAutAct'].value: + self._opmode_to_off() + if value: + self.attributes['StateOffOp'].set_value(False) + + def set_state_op_op(self, value: bool): + _logger.debug(f'{self._name_of_parent}StateOpOp set to {value}') + if not self.attributes['StateChannel'].value and value: + if self.attributes['StateOffAct'].value or self.attributes['StateAutAct'].value: + self._opmode_to_op() + if value: + self.attributes['StateOpOp'].set_value(False) + + def set_state_aut_op(self, value: bool): + _logger.debug(f'{self._name_of_parent}StateAutOp set to {value}') + if not self.attributes['StateChannel'].value and value: + if self.attributes['StateOffAct'].value or self.attributes['StateOpAct'].value: + self._opmode_to_aut() + if value: + self.attributes['StateAutOp'].set_value(False) + + # --- Accessors for operation mode state --- + def is_opmode_off(self) -> bool: + return self.attributes['StateOffAct'].value + + def is_opmode_aut(self) -> bool: + return self.attributes['StateAutAct'].value + + def is_opmode_op(self) -> bool: + return self.attributes['StateOpAct'].value + + +class SourceMode(): + def __init__(self, name_of_parent: str = ''): + """ + Represents the source mode control. Used by the operation elements. + (Table 16 VDI/VDE/NAMUR 2658-3). + """ + if not hasattr(self, 'attributes'): + self.attributes: dict['str', Attribute] = {} + self.attributes.update({ + # --- Source mode control --- + 'SrcChannel': Attribute('SrcChannel', bool, init_value=True, sub_cb=self.set_src_channel), 'SrcManAut': Attribute('SrcManAut', bool, init_value=False, sub_cb=self.set_src_man_aut), - 'SrcIntOp': Attribute('SrcIntOp', bool, init_value=False, sub_cb=self.set_src_int_op), 'SrcIntAut': Attribute('SrcIntAut', bool, init_value=False, sub_cb=self.set_src_int_aut), + 'SrcIntOp': Attribute('SrcIntOp', bool, init_value=False, sub_cb=self.set_src_int_op), 'SrcManOp': Attribute('SrcManOp', bool, init_value=False, sub_cb=self.set_src_man_op), - 'SrcIntAct': Attribute('SrcIntAct', bool, init_value=False), + + 'SrcIntAct': Attribute('SrcIntAct', bool, init_value=True), 'SrcManAct': Attribute('SrcManAct', bool, init_value=False) - } + }) - def _src_to_off(self): - self.attributes['SrcIntAct'].set_value(False) - self.attributes['SrcManAct'].set_value(False) - logging.debug('Source mode is now off') + self._name_of_parent = f"{name_of_parent}: " if name_of_parent != '' else '' + # --- Source mode transitions --- def _src_to_int(self): self.attributes['SrcIntAct'].set_value(True) self.attributes['SrcManAct'].set_value(False) - logging.debug('Source mode is now int') + _logger.debug('Source mode is now int') def _src_to_man(self): self.attributes['SrcIntAct'].set_value(False) self.attributes['SrcManAct'].set_value(True) - logging.debug('Source mode is now man') + _logger.debug('Source mode is now man') + # --- Callbacks for source control --- def set_src_channel(self, value: bool): - logging.debug('Source mode channel is now %s' % value) + _logger.debug('Source mode channel is now %s' % value) + if self.attributes['SrcChannel'].value != value: + self.attributes['SrcChannel'].set_value(value) def set_src_man_aut(self, value: bool): - if not self.attributes['StateOffAct'].value and value: - if self.attributes['SrcChannel'].value: - self._src_to_man() + if self.attributes['SrcChannel'].value and value: + self._src_to_man() def set_src_man_op(self, value: bool): - if not self.attributes['StateOffAct'].value and value: - if not self.attributes['SrcChannel'].value: - self._src_to_man() - self.attributes['SrcManOp'].set_value(False) + if not self.attributes['SrcChannel'].value and value: + self._src_to_man() + if value: + self.attributes['SrcManOp'].set_value(False) + + def set_src_int_aut(self, value: bool): + if self.attributes['SrcChannel'].value and value: + self._src_to_int() + + def set_src_int_op(self, value: bool): + if not self.attributes['SrcChannel'].value and value: + self._src_to_int() + if value: + self.attributes['SrcIntOp'].set_value(False) + + # --- Accessors for source mode state --- + def is_srcmode_int(self) -> bool: + return self.attributes['SrcIntAct'].value + + def is_srcmode_man(self) -> bool: + return self.attributes['SrcManAct'].value + + +class OperationSourceModeElement(OperationMode, SourceMode): + def __init__(self, name_of_parent: str = ''): + """ + Represents the operation and source mode control for operation elements. + """ + OperationMode.__init__(self, name_of_parent) + SourceMode.__init__(self, name_of_parent) diff --git a/src/mtppy/parameter_elements.py b/src/mtppy/parameter_elements.py new file mode 100644 index 0000000..1ccb017 --- /dev/null +++ b/src/mtppy/parameter_elements.py @@ -0,0 +1,266 @@ +import logging + +from mtppy.attribute import Attribute +from mtppy.operation_source_mode import OperationSourceMode +from mtppy.suc_data_assembly import SUCParameterElement + +_logger = logging.getLogger(__name__) + + +class AnaServParam(SUCParameterElement): + def __init__(self, tag_name: str, tag_description: str = '', v_min: float = 0, v_max: float = 100, + v_scl_min: float = 0, v_scl_max: float = 100, v_unit: int = 0, init_value: float = None): + """ + Analog Service Parameter (AnaServParam). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. + """ + super().__init__(tag_name, tag_description) + + self.op_src_mode = OperationSourceMode() + + self.v_min = v_min + self.v_max = v_max + self.v_scl_min = v_scl_min + self.v_scl_max = v_scl_max + self.v_unit = v_unit + if init_value is None: + init_value = v_min + + self._add_attribute(Attribute('VOp', float, init_value=init_value, sub_cb=self.set_v_op)) + self._add_attribute(Attribute('VInt', float, init_value=init_value, sub_cb=self.set_v_int)) + self._add_attribute(Attribute('VExt', float, init_value=init_value, sub_cb=self.set_v_ext)) + self._add_attribute(Attribute('VReq', float, init_value=init_value)) + self._add_attribute(Attribute('VOut', float, init_value=0)) + self._add_attribute(Attribute('VFbk', float, init_value=0)) + self._add_attribute(Attribute('VUnit', int, init_value=self.v_unit)) + self._add_attribute(Attribute('VSclMin', float, init_value=self.v_scl_min)) + self._add_attribute(Attribute('VSclMax', float, init_value=self.v_scl_max)) + self._add_attribute(Attribute('VMin', float, init_value=self.v_min)) + self._add_attribute(Attribute('VMax', float, init_value=self.v_max)) + self._add_attribute(Attribute('Sync', bool, False)) + + def set_v_op(self, value): + _logger.debug('VOp set to %s' % value) + if self.op_src_mode.attributes['StateOpAct'].value: + self.set_v_req(value) + + def set_v_int(self, value): + _logger.debug('VInt set to %s' % value) + if self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcIntAct'].value: + self.set_v_req(value) + + def set_v_ext(self, value): + _logger.debug('VExt set to %s' % value) + if self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcExtAct'].value: + self.set_v_req(value) + + def valid_value(self, value): + if value < self.v_min or value > self.v_max: + return False + else: + return True + + def set_v_req(self, value): + if self.valid_value(value): + self.attributes['VReq'].set_value(value) + _logger.debug('VReq set to %s' % value) + else: + _logger.debug('VReq cannot be set to %s (out of range)' % value) + + def set_v_out(self): + v_req = self.attributes['VReq'].value + self.attributes['VOut'].set_value(v_req) + self.set_v_fbk(v_req) + _logger.debug('VOut set to %s' % v_req) + + def get_v_out(self): + return self.attributes['VOut'].value + + def set_v_fbk(self, value): + self.attributes['VFbk'].set_value(value) + _logger.debug('VFbk set to %s' % value) + + +class BinServParam(SUCParameterElement): + def __init__(self, tag_name: str, tag_description: str = '', v_state_0: str = 'false', v_state_1: str = 'true', init_value: bool = False): + """ + Binary Service Parameter (BinServParam). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. + """ + super().__init__(tag_name, tag_description) + + self.op_src_mode = OperationSourceMode() + + self.v_state_0 = v_state_0 + self.v_state_1 = v_state_1 + + self._add_attribute(Attribute('VOp', bool, init_value=init_value, sub_cb=self.set_v_op)) + self._add_attribute(Attribute('VInt', bool, init_value=init_value, sub_cb=self.set_v_int)) + self._add_attribute(Attribute('VExt', bool, init_value=init_value, sub_cb=self.set_v_ext)) + self._add_attribute(Attribute('VReq', bool, init_value=init_value)) + self._add_attribute(Attribute('VOut', bool, init_value=False)) + self._add_attribute(Attribute('VFbk', bool, init_value=False)) + self._add_attribute(Attribute('VState0', str, init_value=self.v_state_0)) + self._add_attribute(Attribute('VState1', str, init_value=self.v_state_1)) + self._add_attribute(Attribute('Sync', bool, False)) + + def set_v_op(self, value): + _logger.debug('VOp set to %s' % value) + if self.op_src_mode.attributes['StateOpAct'].value: + self.set_v_req(value) + + def set_v_int(self, value): + _logger.debug('VInt set to %s' % value) + if self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcIntAct'].value: + self.set_v_req(value) + + def set_v_ext(self, value): + _logger.debug('VExt set to %s' % value) + if self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcExtAct'].value: + self.set_v_req(value) + + def set_v_req(self, value): + self.attributes['VReq'].set_value(value) + _logger.debug('VReq set to %s' % value) + + def set_v_out(self): + v_req = self.attributes['VReq'].value + self.attributes['VOut'].set_value(v_req) + self.set_v_fbk(v_req) + _logger.debug('VOut set to %s' % v_req) + + def get_v_out(self): + return self.attributes['VOut'].value + + def set_v_fbk(self, value): + self.attributes['VFbk'].set_value(value) + _logger.debug('VFbk set to %s' % value) + + +class DIntServParam(SUCParameterElement): + def __init__(self, tag_name: str, tag_description: str = '', v_min: int = 0, v_max: int = 100, v_scl_min: int = 0, + v_scl_max: int = 100, v_unit: int = 0, init_value: int = None): + """ + Discrete Integer Service Parameter (DIntServParam). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. + """ + super().__init__(tag_name, tag_description) + + self.op_src_mode = OperationSourceMode(tag_name) + + self.v_min = v_min + self.v_max = v_max + self.v_scl_min = v_scl_min + self.v_scl_max = v_scl_max + self.v_unit = v_unit + if init_value is None: + init_value = v_min + + self._add_attribute(Attribute('VOp', int, init_value=init_value, sub_cb=self.set_v_op)) + self._add_attribute(Attribute('VInt', int, init_value=init_value, sub_cb=self.set_v_int)) + self._add_attribute(Attribute('VExt', int, init_value=init_value, sub_cb=self.set_v_ext)) + self._add_attribute(Attribute('VReq', int, init_value=init_value)) + self._add_attribute(Attribute('VOut', int, init_value=0)) + self._add_attribute(Attribute('VFbk', int, init_value=0)) + self._add_attribute(Attribute('VUnit', int, init_value=self.v_unit)) + self._add_attribute(Attribute('VSclMin', int, init_value=self.v_scl_min)) + self._add_attribute(Attribute('VSclMax', int, init_value=self.v_scl_max)) + self._add_attribute(Attribute('VMin', int, init_value=self.v_min)) + self._add_attribute(Attribute('VMax', int, init_value=self.v_max)) + self._add_attribute(Attribute('Sync', bool, False)) + + def set_v_op(self, value): + _logger.debug('VOp set to %s' % value) + if self.op_src_mode.attributes['StateOpAct'].value: + self.set_v_req(value) + + def set_v_int(self, value): + _logger.debug('VInt set to %s' % value) + if (self.op_src_mode.attributes['StateAutAct'].value + and self.op_src_mode.attributes['SrcIntAct'].value): + self.set_v_req(value) + + def set_v_ext(self, value): + _logger.debug('VExt set to %s' % value) + if (self.op_src_mode.attributes['StateAutAct'].value + and self.op_src_mode.attributes['SrcExtAct'].value): + self.set_v_req(value) + + def valid_value(self, value): + if value < self.v_min or value > self.v_max: + return False + else: + return True + + def set_v_req(self, value): + if self.valid_value(value): + self.attributes['VReq'].set_value(value) + _logger.debug('VReq set to %s' % value) + else: + _logger.debug('VReq cannot be set to %s (out of range)' % value) + + def set_v_out(self): + v_req = self.attributes['VReq'].value + self.attributes['VOut'].set_value(v_req) + self.set_v_fbk(v_req) + _logger.debug('VOut set to %s' % v_req) + + def get_v_out(self): + return self.attributes['VOut'].value + + def set_v_fbk(self, value): + self.attributes['VFbk'].set_value(value) + _logger.debug('VFbk set to %s' % value) + + +class StringServParam(SUCParameterElement): + def __init__(self, tag_name: str, tag_description: str = '', init_value: str = ''): + """ + String Service Parameter (StringServParam). Parameter names correspond attribute names in VDI/VDE/NAMUR 2658. + """ + super().__init__(tag_name, tag_description) + + self.op_src_mode = OperationSourceMode() + + self._add_attribute(Attribute('VOp', str, init_value=init_value, sub_cb=self.set_v_op)) + self._add_attribute(Attribute('VInt', str, init_value=init_value, sub_cb=self.set_v_int)) + self._add_attribute(Attribute('VExt', str, init_value=init_value, sub_cb=self.set_v_ext)) + self._add_attribute(Attribute('VReq', str, init_value=init_value)) + self._add_attribute(Attribute('VOut', str, init_value='')) + self._add_attribute(Attribute('VFbk', str, init_value='')) + self._add_attribute(Attribute('Sync', bool, False)) + + def set_v_op(self, value): + _logger.debug('VOp set to %s' % value) + if self.op_src_mode.attributes['StateOpAct'].value: + self.set_v_req(value) + + def set_v_int(self, value): + _logger.debug('VInt set to %s' % value) + if self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcIntAct'].value: + self.set_v_req(value) + + def set_v_ext(self, value): + _logger.debug('VExt set to %s' % value) + if self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcExtAct'].value: + self.set_v_req(value) + + def valid_value(self, value): + return True + + def set_v_req(self, value): + if self.valid_value(value): + self.attributes['VReq'].set_value(value) + _logger.debug('VReq set to %s' % value) + else: + _logger.debug('VReq cannot be set to %s (out of range)' % value) + + def set_v_out(self): + v_req = self.attributes['VReq'].value + self.attributes['VOut'].set_value(v_req) + self.set_v_fbk(v_req) + _logger.debug('VOut set to %s' % v_req) + + def get_v_out(self): + return self.attributes['VOut'].value + + def set_v_fbk(self, value): + self.attributes['VFbk'].set_value(value) + _logger.debug('VFbk set to %s' % value) diff --git a/src/mtppy/procedure.py b/src/mtppy/procedure.py index 20fc8ad..4d92bab 100644 --- a/src/mtppy/procedure.py +++ b/src/mtppy/procedure.py @@ -2,48 +2,66 @@ from mtppy.suc_data_assembly import * +_logger = logging.getLogger(__name__) + class Procedure(SUCServiceProcedure): def __init__(self, procedure_id: int, tag_name: str, tag_description: str = '', is_self_completing: bool = False, is_default: bool = False): """ Represents a procedure of a service. - :param procedure_id: Procedure id. - :param tag_name: Tag name of the procedure. - :param tag_description: Tag description of the procedure. - :param is_self_completing: Self-completing or not. - :param is_default: Default or not. + + Args: + procedure_id (int): Procedure id. Can't be equal or less than 0. + tag_name (str): Tag name of the procedure. + tag_description (str): Tag description of the procedure. + is_self_completing (bool): Self-completing or not. + is_default (bool): Default or not. """ + if procedure_id <= 0: + raise ValueError(f"{tag_name}: Procedure ID can't be equal or less than 0.") super().__init__(procedure_id, tag_name, tag_description, is_self_completing, is_default) - self.procedure_parameters = {} + self.procedure_parameters: dict[str, SUCParameterElement] = {} self.process_value_ins = {} - self.report_values = {} - self.process_value_outs = {} + self.report_values: dict[str, SUCIndicatorElement] = {} + self.process_value_outs: dict[str, SUCIndicatorElement] = {} - def add_procedure_parameter(self, procedure_parameter: SUCOperationElement): + def add_procedure_parameter(self, procedure_parameter: SUCParameterElement): """ Adds a procedure parameter to the procedure. - :param procedure_parameter: Procedure parameter. - :return: + + Args: + procedure_parameter (SUCParameterElement): Procedure parameter. + + Raises: + TypeError: If procedure_parameter is not an instance of SUCParameterElement. """ - if isinstance(procedure_parameter, SUCOperationElement): + if isinstance(procedure_parameter, SUCParameterElement): self.procedure_parameters[procedure_parameter.tag_name] = procedure_parameter else: raise TypeError() def add_procedure_value_in(self, process_value_in): """ - Adds an value in to the procedure. NOT IMPLEMENTED. - :param process_value_in: Value in. - :return: + Adds a value in to the procedure. NOT IMPLEMENTED. + + Args: + process_value_in: Value in. + + Raises: + NotImplementedError: Always raised as the method is not implemented. """ raise NotImplementedError() def add_report_value(self, report_value: SUCIndicatorElement): """ Adds a report value to the procedure. - :param report_value: Report value. - :return: + + Args: + report_value (SUCIndicatorElement): Report value. + + Raises: + TypeError: If report_value is not an instance of SUCIndicatorElement. """ if isinstance(report_value, SUCIndicatorElement): self.report_values[report_value.tag_name] = report_value @@ -52,9 +70,13 @@ def add_report_value(self, report_value: SUCIndicatorElement): def add_procedure_value_out(self, process_value_out: SUCIndicatorElement): """ - Adds an value out to the procedure. - :param process_value_out: Value in. - :return: + Adds a value out to the procedure. + + Args: + process_value_out (SUCIndicatorElement): Value out. + + Raises: + TypeError: If process_value_out is not an instance of SUCIndicatorElement. """ if isinstance(process_value_out, SUCIndicatorElement): self.process_value_outs[process_value_out.tag_name] = process_value_out @@ -64,8 +86,7 @@ def add_procedure_value_out(self, process_value_out: SUCIndicatorElement): def apply_procedure_parameters(self): """ Applies procedure parameters. - :return: """ - logging.debug('Applying procedure parameters') + _logger.debug('Applying procedure parameters') for procedure_parameter in self.procedure_parameters.values(): procedure_parameter.set_v_out() diff --git a/src/mtppy/procedure_control.py b/src/mtppy/procedure_control.py index 566ed6f..4d947b8 100644 --- a/src/mtppy/procedure_control.py +++ b/src/mtppy/procedure_control.py @@ -3,13 +3,17 @@ from mtppy.attribute import Attribute from mtppy.operation_source_mode import OperationSourceMode +_logger = logging.getLogger(__name__) + class ProcedureControl: def __init__(self, procedures: dict, service_op_src_mode: OperationSourceMode): """ Represents the procedure control. - :param procedures: Procedures. - :param service_op_src_mode: Operation and source mode of the service. + + Args: + procedures (dict): Procedures. + service_op_src_mode (OperationSourceMode): Operation and source mode of the service. """ self.attributes = { 'ProcedureOp': Attribute('ProcedureOp', int, init_value=0, sub_cb=self.set_procedure_op), @@ -23,21 +27,33 @@ def __init__(self, procedures: dict, service_op_src_mode: OperationSourceMode): self.op_src_mode = service_op_src_mode self.default_procedure_id = None + # will be set to True when resetting procedure commands to avoid also setting ProcedureReq to 0 + self.__resetting_procedure_commands = 0 + def set_procedure_op(self, value: int): - logging.debug('ProcedureOP set to %s' % value) + _logger.debug('ProcedureOP set to %s' % value) if self.op_src_mode.attributes['StateOpAct'].value: self.set_procedure_req(value) def set_procedure_int(self, value: int): - logging.debug('ProcedureInt set to %s' % value) + _logger.debug('ProcedureInt set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcIntAct'].value: self.set_procedure_req(value) def set_procedure_ext(self, value: int): - logging.debug('ProcedureExt set to %s' % value) + _logger.debug('ProcedureExt set to %s' % value) if self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcExtAct'].value: self.set_procedure_req(value) + def reset_procedure_commands(self): + # ignore the next two invalid ProcedureReq settings + # one from direct setting and one from data change callback + # TODO: improve this logic + self.__resetting_procedure_commands = 2 + self.attributes['ProcedureOp'].set_value(0) + self.attributes['ProcedureInt'].set_value(0) + self.attributes['ProcedureExt'].set_value(0) + def valid_value(self, value: int): if value not in self.procedures.keys(): return False @@ -47,17 +63,35 @@ def valid_value(self, value: int): def set_procedure_req(self, value: int): if self.valid_value(value): self.attributes['ProcedureReq'].set_value(value) - logging.debug('ProcedureReq set to %s' % value) - else: - logging.debug('ProcedureReq cannot be set to %s (out of range)' % value) + _logger.debug('ProcedureReq set to %s' % value) + return + if self.__resetting_procedure_commands > 0: + self.__resetting_procedure_commands -= 1 + return + + # invalid value -> reset ProcedureReq to 0 + self.attributes['ProcedureReq'].set_value(0) + if value != 0: + _logger.warning('ProcedureReq cannot be set to %s (out of range)' % value) def set_procedure_cur(self): procedure_req = self.attributes['ProcedureReq'].value + + if not self.valid_value(procedure_req): + if procedure_req == 0: + raise ValueError("No procedure requested. Please set a valid procedure ID.") + else: + raise ValueError( + f"No procedure with ID {procedure_req} exists. Please set a valid procedure ID.") + self.attributes['ProcedureCur'].set_value(procedure_req) - logging.debug('ProcedureCur set to %s' % procedure_req) + _logger.debug('ProcedureCur set to %s' % procedure_req) def get_procedure_cur(self): return self.attributes['ProcedureCur'].value + def get_procedure_req(self): + return self.attributes['ProcedureReq'].value + def apply_procedure_parameters(self): self.procedures[self.get_procedure_cur()].apply_procedure_parameters() diff --git a/src/mtppy/service.py b/src/mtppy/service.py index 16f79fb..722fc2a 100644 --- a/src/mtppy/service.py +++ b/src/mtppy/service.py @@ -1,50 +1,215 @@ import logging +import time +from threading import Event +from collections.abc import Callable from abc import abstractmethod from mtppy.suc_data_assembly import SUCServiceControl -from mtppy.thread_control import ThreadControl +from mtppy.thread_control import ThreadControl, StoppableThread from mtppy.operation_source_mode import OperationSourceMode from mtppy.state_machine import StateMachine from mtppy.procedure_control import ProcedureControl -from mtppy.state_codes import StateCodes from mtppy.procedure import Procedure -from mtppy.suc_data_assembly import SUCOperationElement +from mtppy.suc_data_assembly import SUCParameterElement -StateCodes = StateCodes() +_logger = logging.getLogger(__name__) class Service(SUCServiceControl): - def __init__(self, tag_name: str, tag_description: str): + """Represents a service of the PEA. + + To create a new service, inherit from this class and implement at least the abstract methods. + These include: + - starting + - execute + - completing + + Additionally, you may override other state methods to implement specific behavior for + those states. Those include: + - completed + - stopping + - stopped + - aborting + - aborted + - resetting + + Some states are part of control loops that can be enabled or disabled. These states include: + - pause_loop + - pausing + - paused + - resuming + - hold_loop + - holding + - held + - unholding + + Methods ending with "ing" are transitional states. After running once they will automatically + transition to the next state. + If a function runs in a loop, it must check for the current state using `is_state(state_str)` + or it must check for the stop event using `get_state_stop_event()`. Because those indicate + that a state transition has been requested and the function must return. + """ + + def __init__(self, tag_name: str, tag_description: str, exception_callback: Callable[[Exception], None] = None): """ Represents a service of the PEA. - :param tag_name: Tag name of the service. - :param tag_description: Tag description of the service. + + Args: + tag_name (str): Tag name of the service. + tag_description (str): Tag description of the service. + exception_callback (Callable[[Exception], None]): Function to call + when an exception occurs in the thread. If None just logs the exception. """ super().__init__(tag_name, tag_description) + self.exception = None - self.thread_ctrl = ThreadControl() - self.op_src_mode = OperationSourceMode() + self.op_src_mode = OperationSourceMode(tag_name) self.configuration_parameters = {} - self.procedures = {} + self.procedures: dict[int, Procedure] = {} self.procedure_control = ProcedureControl(self.procedures, self.op_src_mode) self.state_machine = StateMachine(operation_source_mode=self.op_src_mode, procedure_control=self.procedure_control, - execution_routine=self.state_change_callback) + execution_routine=self._state_change_callback) + + self.thread_ctrl = ThreadControl(service_name=tag_name, + state_change_function=self._state_change, + exception_callback=exception_callback) self.op_src_mode.add_enter_offline_callback(self.state_machine.command_en_ctrl.disable_all) + self.op_src_mode.add_enter_offline_callback(self.state_machine.update_command_en) + self.op_src_mode.add_enter_offline_callback(self.thread_ctrl.stop_thread) self.op_src_mode.add_exit_offline_callback(self.state_machine.command_en_ctrl.set_default) - self.op_src_mode.add_exit_offline_callback(self.apply_configuration_parameters) - self.op_src_mode.add_exit_offline_callback(self.init_idle_state) + self.op_src_mode.add_exit_offline_callback(self.state_machine.update_command_en) + self.op_src_mode.add_exit_offline_callback(self._apply_configuration_parameters) + self.op_src_mode.add_exit_offline_callback(self._init_idle_state) + + self.op_src_mode.add_enter_operator_callback( + # adds function to disable commands if no procedure is set + # lambda allows adding function with argument + lambda: self.procedure_control.attributes['ProcedureReq'].attach_subscription_callback( + self.state_machine.disable_commands_if_no_procedure) + ) + self.op_src_mode.add_enter_operator_callback( + lambda: self.state_machine.disable_commands_if_no_procedure( + self.procedure_control.attributes['ProcedureReq'].value + ) + ) + + self.op_src_mode.add_exit_operator_callback( + # removes function to disables commands if no procedure is set + self.procedure_control.attributes['ProcedureReq'].remove_subscription_callback + ) + + def enable_pause_loop(self, enable: bool = True): + """ + Enables or disables the pause loop. + + Args: + enable (bool): If True, enables the pause loop. If False, disables it. + """ + self.state_machine.command_en_ctrl.enable_pause_loop(enable) + + def enable_hold_loop(self, enable: bool = True): + """ + Enables or disables the hold loop. + + Args: + enable (bool): If True, enables the hold loop. If False, disables it. + """ + self.state_machine.command_en_ctrl.enable_hold_loop(enable) + + def enable_restart(self, enable: bool = True): + """ + Enables or disables the restart command. + + Args: + enable (bool): If True, enables the restart command. If False, disables it. + """ + self.state_machine.command_en_ctrl.enable_restart(enable) + + def add_configuration_parameter(self, configuration_parameter: SUCParameterElement): + """ + Adds a configuration parameter to the service. + + Args: + configuration_parameter (SUCParameterElement): Configuration parameter to add. + """ + self.configuration_parameters[configuration_parameter.tag_name] = configuration_parameter + + def add_procedure(self, procedure: Procedure): + """ + Adds a procedure to the service. + + Args: + procedure (Procedure): Procedure to add. + """ + self.procedures[procedure.attributes['ProcedureId'].value] = procedure + if procedure.attributes['IsDefault'].value: + self.procedure_control.default_procedure_id = procedure.attributes['ProcedureId'].value + self.procedure_control.attributes['ProcedureOp'].init_value = self.procedure_control.default_procedure_id + self.procedure_control.attributes['ProcedureInt'].init_value = self.procedure_control.default_procedure_id + self.procedure_control.attributes['ProcedureExt'].init_value = self.procedure_control.default_procedure_id + self.procedure_control.attributes['ProcedureReq'].init_value = self.procedure_control.default_procedure_id + self.procedure_control.set_procedure_req(self.procedure_control.default_procedure_id) + + def get_current_procedure(self) -> Procedure: + """ + Returns the current procedure. + + Returns: + Procedure: The current procedure. + """ + return self.procedures[self.procedure_control.get_procedure_cur()] + + def is_state(self, state_str): + """ + Checks if the current state matches the given state. + + Args: + state_str (str): State to check. + + Returns: + bool: True if the current state matches, False otherwise. + """ + if state_str is self.state_machine.get_current_state_str(): + # handel the case where there are multiple threads for the same state are running + # (e.g. execute state after restart) + if self.get_state_stop_event().is_set(): + return False + return True + else: + # start the next thread if the state is not the current one + self.thread_ctrl.reallocate_running_thread() + return False + + def get_state_stop_event(self) -> Event: + """ + Returns an event that is set when the state should stop. + + Returns: + Event: Event that is set when the state should stop. + """ + current_thread = self.thread_ctrl.get_current_thread() + if not isinstance(current_thread, StoppableThread): + raise RuntimeError("Current thread is not a state thread. No stop event available.") + + return current_thread.stop_event - def init_idle_state(self): - self.state_change_callback() + def _init_idle_state(self): + """ + Initializes the idle state. + """ + self._state_change_callback() - def state_change_callback(self): + def _state_change_callback(self): + """ + Callback for state changes. + """ if self.op_src_mode.attributes['StateOffAct'].value: return @@ -57,67 +222,54 @@ def state_change_callback(self): else: self.op_src_mode.allow_switch_to_offline_mode(False) - def is_state(self, state_str): - if state_str is self.state_machine.get_current_state_str(): - return True - else: - self.thread_ctrl.reallocate_running_thread() - return False - - def state_change(self): + def _state_change(self): + """ + Changes the state. Is automatically called after each state function returns. + """ + # don't automatically change state for non self-completing procedures + if self.state_machine.get_current_state_str() == "execute": + if not self._is_self_completing(): + return self.state_machine.state_change() - def add_configuration_parameter(self, configuration_parameter: SUCOperationElement): + def _is_self_completing(self) -> bool: """ - Adds a configuration parameter to the service. - :param configuration_parameter: Configuration parameter. - :return: + Checks if the current Procedure is self-completing. + + Returns: + bool: True if the current Procedure is self-completing, False otherwise. """ - self.configuration_parameters[configuration_parameter.tag_name] = configuration_parameter + return self.get_current_procedure().attributes['IsSelfCompleting'].value - def apply_configuration_parameters(self): + def _apply_configuration_parameters(self): """ Applies configuration parameters. - :return: """ - logging.debug('Applying service configuration parameters') + _logger.debug('Applying service configuration parameters') for configuration_parameter in self.configuration_parameters.values(): configuration_parameter.set_v_out() - def add_procedure(self, procedure: Procedure): - """ - Adds procedure to the service. - :param procedure: Procedure. - :return: - """ - self.procedures[procedure.attributes['ProcedureId'].value] = procedure - if procedure.attributes['IsDefault'].value: - self.procedure_control.default_procedure_id = procedure.attributes['ProcedureId'].value - self.procedure_control.attributes['ProcedureOp'].init_value = self.procedure_control.default_procedure_id - self.procedure_control.attributes['ProcedureInt'].init_value = self.procedure_control.default_procedure_id - self.procedure_control.attributes['ProcedureExt'].init_value = self.procedure_control.default_procedure_id - - @abstractmethod def idle(self): """ Idle state. - :return: """ - pass + _logger.debug(f"{self.tag_name} - Idle -") + cycle = 0 + while self.is_state("idle"): + _logger.debug(f"{self.tag_name} - Idle cycle {cycle}") + cycle += 1 + time.sleep(3) @abstractmethod def starting(self): """ Starting state. - :return: """ - pass @abstractmethod def execute(self): """ Execute state. - :return: """ pass @@ -125,102 +277,89 @@ def execute(self): def completing(self): """ Completing state. - :return: """ pass - @abstractmethod def completed(self): """ Completed state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "completed": + _logger.debug(f"{self.tag_name} - Completed -") - @abstractmethod def pausing(self): """ Pausing state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "pausing": + _logger.debug(f"{self.tag_name} - Pausing -") - @abstractmethod def paused(self): """ Paused state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "paused": + _logger.debug(f"{self.tag_name} - Paused -") - @abstractmethod def resuming(self): """ Resuming state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "resuming": + _logger.debug(f"{self.tag_name} - Resuming -") - @abstractmethod def holding(self): """ Holding state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "holding": + _logger.debug(f"{self.tag_name} - Holding -") - @abstractmethod def held(self): """ Held state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "held": + _logger.debug(f"{self.tag_name} - Held -") - @abstractmethod def unholding(self): """ Unholding state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "unholding": + _logger.debug(f"{self.tag_name} - Unholding -") - @abstractmethod def stopping(self): """ Stopping state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "stopping": + _logger.debug(f"{self.tag_name} - Stopping -") - @abstractmethod def stopped(self): """ Stopped state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "stopped": + _logger.debug(f"{self.tag_name} - Stopped -") - @abstractmethod def aborting(self): """ Aborting state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "aborting": + _logger.debug(f"{self.tag_name} - Aborting -") - @abstractmethod def aborted(self): """ Aborted state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "aborted": + _logger.debug(f"{self.tag_name} - Aborted -") - @abstractmethod def resetting(self): """ Resetting state. - :return: """ - pass + if self.state_machine.get_current_state_str() == "resetting": + _logger.debug(f"{self.tag_name} - Resetting -") diff --git a/src/mtppy/state_codes.py b/src/mtppy/state_codes.py index 0c51c5d..0a6c349 100644 --- a/src/mtppy/state_codes.py +++ b/src/mtppy/state_codes.py @@ -1,9 +1,9 @@ class StateCodes: def __init__(self): """ - List of state codes as int and string. + Initializes the StateCodes class with state codes as integers and strings. """ - #self.undefined = 1 + # self.undefined = 1 self.stopped = 4 self.starting = 8 self.idle = 16 @@ -17,12 +17,12 @@ def __init__(self): self.unholding = 4096 self.pausing = 8192 self.resuming = 16384 - self.resetting = 32678 + self.resetting = 32768 self.completing = 65536 self.completed = 131072 self.int_code = {} - #self.int_code[1] ='undefined' + # self.int_code[1] ='undefined' self.int_code[4] = 'stopped' self.int_code[8] = 'starting' self.int_code[16] = 'idle' @@ -36,20 +36,24 @@ def __init__(self): self.int_code[4096] = 'unholding' self.int_code[8192] = 'pausing' self.int_code[16384] = 'resuming' - self.int_code[32678] = 'resetting' + self.int_code[32768] = 'resetting' self.int_code[65536] = 'completing' self.int_code[131072] = 'completed' def get_list_int(self): """ - Gets a list of state as int. - :return: List of states. + Gets a list of state codes as integers. + + Returns: + list: List of state codes as integers. """ return list(self.int_code.keys()) def get_list_str(self): """ - Gets a list of state as string.. - :return: List of states. + Gets a list of state codes as strings. + + Returns: + list: List of state codes as strings. """ return list(self.int_code.values()) diff --git a/src/mtppy/state_machine.py b/src/mtppy/state_machine.py index 7762c6a..250a4b0 100644 --- a/src/mtppy/state_machine.py +++ b/src/mtppy/state_machine.py @@ -1,5 +1,7 @@ import logging +from collections.abc import Callable + from mtppy.attribute import Attribute from mtppy.state_codes import StateCodes from mtppy.command_codes import CommandCodes @@ -9,16 +11,20 @@ StateCodes = StateCodes() CommandCodes = CommandCodes() +_logger = logging.getLogger(__name__) + class StateMachine: def __init__(self, operation_source_mode: OperationSourceMode, procedure_control: ProcedureControl, - execution_routine: callable): + execution_routine: Callable[[], None]): """ Represents a state machine for a service. - :param operation_source_mode: Operation and source mode control. - :param procedure_control: Procedure control. - :param execution_routine: Execution routine for state changing. + + Args: + operation_source_mode (OperationSourceMode): Operation and source mode control. + procedure_control (ProcedureControl): Procedure control. + execution_routine (Callable): Execution routine for state changing. """ self.attributes = { @@ -30,9 +36,10 @@ def __init__(self, operation_source_mode: OperationSourceMode, 'CommandEn': Attribute('CommandEn', int, init_value=0), } - self.op_src_mode = operation_source_mode - self.procedure_control = procedure_control + self.op_src_mode: OperationSourceMode = operation_source_mode + self.procedure_control: ProcedureControl = procedure_control self.execution_routine = execution_routine + self.command_en_ctrl = CommandEnControl() self.act_state = StateCodes.idle @@ -51,27 +58,46 @@ def set_command_ext(self, value: int): self.command_execution(value) def command_execution(self, com_var: int): + # skip if command code was just reset to 0 + if com_var == 0: + return + if com_var not in CommandCodes.get_list_int(): - logging.debug(f'Command Code {com_var} does not exist') + _logger.warning(f'Command Code {com_var} does not exist') return cmd_str = CommandCodes.int_code[com_var] if not self.command_en_ctrl.is_enabled(cmd_str): - logging.debug(f'CommandEn does not permit to execute {cmd_str} from state {self.get_current_state_str()}') + _logger.debug( + f'CommandEn does not permit to execute {cmd_str} from state {self.get_current_state_str()}') return else: - logging.debug(f'CommandEn permits to execute {cmd_str}') + _logger.debug(f'CommandEn permits to execute {cmd_str}') + # execute the command, if it is enabled will change the state thread to the requested state eval(f'self.{CommandCodes.int_code[com_var]}()') + # reset the command operation code + if self.op_src_mode.attributes['StateOpAct'].value: + self.attributes['CommandOp'].set_value(0) + elif self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcIntAct'].value: + self.attributes['CommandInt'].set_value(0) + elif self.op_src_mode.attributes['StateAutAct'].value and self.op_src_mode.attributes['SrcExtAct'].value: + self.attributes['CommandExt'].set_value(0) + def start(self): if self.command_en_ctrl.is_enabled('start'): + self.procedure_control.set_procedure_cur() + self.procedure_control.reset_procedure_commands() self.procedure_control.apply_procedure_parameters() self._change_state_to(StateCodes.starting) def restart(self): if self.command_en_ctrl.is_enabled('restart'): + self.procedure_control.set_procedure_cur() + self.procedure_control.reset_procedure_commands() + self.procedure_control.apply_procedure_parameters() self._change_state_to(StateCodes.starting) def complete(self): @@ -111,7 +137,7 @@ def state_change(self): self._change_state_to(StateCodes.execute) elif self.act_state == StateCodes.starting: self._change_state_to(StateCodes.execute) - elif selt.act_state == StateCodes.execute: + elif self.act_state == StateCodes.execute: self._change_state_to(StateCodes.completing) elif self.act_state == StateCodes.completing: self._change_state_to(StateCodes.completed) @@ -121,6 +147,9 @@ def state_change(self): self._change_state_to(StateCodes.execute) elif self.act_state == StateCodes.resetting: self._change_state_to(StateCodes.idle) + # fix for commands being enabled when procedure is not set + self.disable_commands_if_no_procedure( + self.procedure_control.attributes['ProcedureReq'].value) elif self.act_state == StateCodes.holding: self._change_state_to(StateCodes.held) elif self.act_state == StateCodes.unholding: @@ -131,13 +160,53 @@ def state_change(self): self._change_state_to(StateCodes.aborted) def _change_state_to(self, new_state: int): + self.prev_state = self.act_state self.act_state = new_state self.attributes['StateCur'].set_value(new_state) new_state_str = StateCodes.int_code[new_state] self.command_en_ctrl.execute(new_state_str) - self.attributes['CommandEn'].set_value(self.command_en_ctrl.get_command_en()) + self.update_command_en() self.execution_routine() - logging.debug(f'Service state changed to {new_state}') + _logger.debug(f'Service state changed to {new_state}') + + def update_command_en(self): + """ + Updates the CommandEn attribute based on the current enabled flags. + """ + self.attributes['CommandEn'].set_value(self.command_en_ctrl.get_command_en()) + + def get_current_state_str(self) -> str: + """ + Get the current state as a string. - def get_current_state_str(self): + Returns: + str: Current state as a string. + """ return StateCodes.int_code[self.act_state] + + def get_prev_state_str(self) -> str: + """ + Get the previous state as a string. + + Returns: + str: Previous state as a string. + """ + return StateCodes.int_code[self.prev_state] + + def disable_commands_if_no_procedure(self, value: int): + """ + Disables all commands if value is 0 and the current state is idle. + + Args: + value (int): Value of the ProcedureReq attribute. + """ + if value == 0: + self.command_en_ctrl.disable_restart_temporarily() + + if self.act_state is StateCodes.idle: + self.command_en_ctrl.disable_all() + else: + self.command_en_ctrl.restore_restart() + + self.command_en_ctrl.execute(self.get_current_state_str()) + self.update_command_en() diff --git a/src/mtppy/suc_data_assembly.py b/src/mtppy/suc_data_assembly.py index a077ed3..16121e6 100644 --- a/src/mtppy/suc_data_assembly.py +++ b/src/mtppy/suc_data_assembly.py @@ -1,4 +1,5 @@ from mtppy.attribute import Attribute +from abc import abstractmethod class SUCDataAssembly: @@ -20,12 +21,32 @@ def __init__(self, tag_name: str, tag_description: str): self._add_attribute(Attribute('OSLevel', int, init_value=0)) self._add_attribute(Attribute('WQC', int, init_value=255)) + @abstractmethod + def set_v(self, value): + """ + Set the value of the 'V' attribute. + """ + pass + class SUCOperationElement(SUCDataAssembly): def __init__(self, tag_name: str, tag_description: str): super().__init__(tag_name, tag_description) self._add_attribute(Attribute('OSLevel', int, init_value=0)) - self._add_attribute(Attribute('WQC', int, init_value=255)) + + @abstractmethod + def set_v_int(self, value): + """ + Set VInt if StateIntAct. + """ + pass + + @abstractmethod + def get_v_out(self): + """ + Get the current value of VOut (current operation value). + """ + pass class SUCActiveElement(SUCDataAssembly): @@ -35,28 +56,60 @@ def __init__(self, tag_name: str, tag_description: str): self._add_attribute(Attribute('WQC', int, init_value=255)) -class SUCServiceControl(SUCDataAssembly): +class SUCDiagnosticElement(SUCDataAssembly): + def __init__(self, tag_name: str, tag_description: str): + super().__init__(tag_name, tag_description) + self._add_attribute(Attribute('WQC', int, init_value=255)) + + +class SUCServiceElement(SUCDataAssembly): def __init__(self, tag_name: str, tag_description: str): super().__init__(tag_name, tag_description) - self._add_attribute(Attribute('OSLevel', int, init_value=0)) self._add_attribute(Attribute('WQC', int, init_value=255)) + + +class SUCServiceControl(SUCServiceElement): + def __init__(self, tag_name: str, tag_description: str): + super().__init__(tag_name, tag_description) + self._add_attribute(Attribute('OSLevel', int, init_value=0)) self._add_attribute(Attribute('PosTextID', int, init_value=0)) self._add_attribute(Attribute('InteractQuestionID', int, init_value=0)) self._add_attribute(Attribute('InteractAnswerID', int, init_value=0)) -class SUCDiagnosticElement(SUCDataAssembly): +class SUCProcedureHealthView(SUCServiceElement): def __init__(self, tag_name: str, tag_description: str): super().__init__(tag_name, tag_description) - self._add_attribute(Attribute('WQC', int, init_value=255)) -class SUCHealthStateView(SUCDiagnosticElement): +class SUCParameterElement(SUCServiceElement): def __init__(self, tag_name: str, tag_description: str): super().__init__(tag_name, tag_description) + self._add_attribute(Attribute('OSLevel', int, init_value=0)) - -class SUCServiceProcedure(SUCHealthStateView): + @abstractmethod + def set_v_int(self, value): + """ + Set Parameter value if StateIntAct. + """ + pass + + @abstractmethod + def get_v_out(self): + """ + Get the current value of VOut (current parameter value). + """ + pass + + @abstractmethod + def set_v_fbk(self, value): + """ + Set VFbk (feedback value) to the specified value. + """ + pass + + +class SUCServiceProcedure(SUCProcedureHealthView): def __init__(self, procedure_id: int, tag_name: str, tag_description: str, is_self_completing=False, is_default=True): super().__init__(tag_name, tag_description) diff --git a/src/mtppy/thread_control.py b/src/mtppy/thread_control.py index d512082..ab61ddc 100644 --- a/src/mtppy/thread_control.py +++ b/src/mtppy/thread_control.py @@ -1,25 +1,115 @@ import logging -from threading import Thread +from threading import Thread, Event, current_thread +from collections.abc import Callable + +_logger = logging.getLogger(__name__) + + +class StoppableThread(Thread): + """ + A thread that can be told stop by setting an event. + """ + + def __init__(self, target=None, name=None, args=(), kwargs=None): + super().__init__(target=target, name=name, args=args, kwargs=kwargs) + self.stop_event = Event() + + def stop(self): + self.stop_event.set() class ThreadControl: - def __init__(self): + def __init__(self, service_name: str = '', + state_change_function: Callable = None, + exception_callback: Callable[[Exception], None] = None): """ Represents a thread control to be able to run with multithreading. + + Args: + service_name (str): Name of the service. + state_change_function (Callable): Function to call after a state completes. + exception_callback (Callable): Function to call when an exception occurs in the thread. """ - self.thread = None + self.service_name = service_name + self.state_change_function = state_change_function + self.current_thread: StoppableThread = None self.running_state = '' self.requested_state = '' - self.callback_function = None + self.callback_function: Callable = None + self.exception_callback: Callable[[Exception], None] = exception_callback - def request_state(self, state: str, cb_function: callable): - logging.debug(f'State {state} requested') + def request_state(self, state: str, cb_function: Callable): + """ + Requests a state change and sets the callback function. + + Args: + state (str): The requested state. + cb_function (Callable): The callback function to execute. + """ + _logger.debug(f'State {state} requested') self.requested_state = state self.callback_function = cb_function def reallocate_running_thread(self): - logging.debug(f'Reallocate thread to state {self.requested_state}') - if self.requested_state is not self.running_state: - self.thread = Thread(target=self.callback_function) - self.thread.start() + """ + Reallocates the running thread to the requested state. + """ + if (self.requested_state is not self.running_state + or (self.running_state == "idle" and + (self.current_thread is None or not self.current_thread.is_alive()))): + _logger.debug(f'Reallocate thread to state {self.requested_state}') + self.stop_thread(stop_if_current_thread=False) + + new_thread_name = f"{self.service_name}_{self.requested_state}" + self.current_thread = StoppableThread(target=self.run_thread, args=(self.callback_function,), + name=new_thread_name) self.running_state = self.requested_state + self.current_thread.start() + + def run_thread(self, target_function: Callable): + """ + Runs the given target function. If an exception occurs the exception callback is called. + + Args: + target_function (Callable): The function to run in the thread. + """ + try: + try: + target_function() + _logger.debug(f"State {current_thread().name} ended") + # changes state for transitional states, only if the state is the current state. + if self.running_state is target_function.__name__: + # check if the running state actually corresponds the the current state + if current_thread() is not self.current_thread: + _logger.debug(f"Ending of old thread, not changing state") + return + if self.state_change_function: + self.state_change_function() + # Catch InterruptedError thrown by stop events. Should not cause an error. + except InterruptedError: + _logger.debug("Stop event was set, stopping thread execution.") + + except Exception as e: + self.exception_callback(e) if self.exception_callback else _logger.error( + f"Exception in thread {self.current_thread.name}: {e}", exc_info=True) + + def stop_thread(self, stop_if_current_thread: bool = True): + """ + Stops the current thread if it is running. + + Args: + stop_if_current_thread (bool): If True, stops the thread also if it is the current thread. + """ + if self.current_thread and self.current_thread.is_alive(): + if stop_if_current_thread or self.current_thread is not current_thread(): + _logger.debug(f'Stopping thread {self.current_thread.name}') + self.current_thread.stop() + + def get_current_thread(self) -> StoppableThread: + """ + Gets the thread object of the thread where this method is called. + + Returns: + StoppableThread: The current thread object. + """ + return current_thread() diff --git a/tests/test_mtp_generator_instance_hierarchy_services.py b/tests/test_mtp_generator_instance_hierarchy_services.py index f52b1db..97d2413 100644 --- a/tests/test_mtp_generator_instance_hierarchy_services.py +++ b/tests/test_mtp_generator_instance_hierarchy_services.py @@ -7,7 +7,7 @@ from mtppy.mtp_generator import MTPGenerator from mtppy.service import Service from mtppy.procedure import Procedure -from mtppy.operation_elements import * +from mtppy.parameter_elements import * from mtppy.indicator_elements import * from mtppy.active_elements import * diff --git a/tests/test_mtp_generator_instance_list.py b/tests/test_mtp_generator_instance_list.py index b208ec0..62ed396 100644 --- a/tests/test_mtp_generator_instance_list.py +++ b/tests/test_mtp_generator_instance_list.py @@ -6,7 +6,7 @@ from mtppy.mtp_generator import MTPGenerator from mtppy.service import Service from mtppy.procedure import Procedure -from mtppy.operation_elements import * +from mtppy.parameter_elements import * from mtppy.indicator_elements import * from mtppy.active_elements import * import xml.etree.ElementTree as ET diff --git a/tests/test_operation_source_mode.py b/tests/test_operation_source_mode.py index 5babc2e..b29b011 100644 --- a/tests/test_operation_source_mode.py +++ b/tests/test_operation_source_mode.py @@ -1,4 +1,4 @@ -from src.mtppy.operation_elements import OperationSourceMode +from mtppy.parameter_elements import OperationSourceMode def init_op_source_mode(op_mode='off', diff --git a/tests/test_procedure.py b/tests/test_procedure.py index 7719e12..c720364 100644 --- a/tests/test_procedure.py +++ b/tests/test_procedure.py @@ -1,5 +1,5 @@ from src.mtppy.procedure import Procedure -from src.mtppy.operation_elements import * +from mtppy.parameter_elements import * from src.mtppy.indicator_elements import * operation_elements = [AnaServParam('serv_param_ana', v_min=0, v_max=50, v_scl_min=0, v_scl_max=10, v_unit=23),