flexCommunicator 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. flexCommunicator/__init__.py +0 -0
  2. flexCommunicator/clientLibraries/__init__.py +0 -0
  3. flexCommunicator/clientLibraries/flcpy/__init__.py +0 -0
  4. flexCommunicator/clientLibraries/flcpy/action/__init__.py +0 -0
  5. flexCommunicator/clientLibraries/flcpy/action/action.py +85 -0
  6. flexCommunicator/clientLibraries/flcpy/action/action_client.py +70 -0
  7. flexCommunicator/clientLibraries/flcpy/action/goal_handle.py +50 -0
  8. flexCommunicator/clientLibraries/flcpy/codec/ROS2Codec.py +227 -0
  9. flexCommunicator/clientLibraries/flcpy/codec/__init__.py +0 -0
  10. flexCommunicator/clientLibraries/flcpy/commands/__init__.py +0 -0
  11. flexCommunicator/clientLibraries/flcpy/commands/commandProcessor.py +113 -0
  12. flexCommunicator/clientLibraries/flcpy/commands/composer.py +25 -0
  13. flexCommunicator/clientLibraries/flcpy/communication/CommunicationManager.py +579 -0
  14. flexCommunicator/clientLibraries/flcpy/communication/__init__.py +0 -0
  15. flexCommunicator/clientLibraries/flcpy/contracts/__init__.py +0 -0
  16. flexCommunicator/clientLibraries/flcpy/contracts/contractVariable.py +93 -0
  17. flexCommunicator/clientLibraries/flcpy/flexCloud/__init__.py +0 -0
  18. flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudConfiguration.py +16 -0
  19. flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudManager.py +162 -0
  20. flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudOrchestrator.py +248 -0
  21. flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudVariable.py +130 -0
  22. flexCommunicator/clientLibraries/flcpy/flexNode.py +892 -0
  23. flexCommunicator/clientLibraries/flcpy/fmi/__init__.py +0 -0
  24. flexCommunicator/clientLibraries/flcpy/fmi/fmuNode.py +166 -0
  25. flexCommunicator/clientLibraries/flcpy/fmi/node.py +360 -0
  26. flexCommunicator/clientLibraries/flcpy/knowledge/KnowledgeManager.py +288 -0
  27. flexCommunicator/clientLibraries/flcpy/knowledge/__init__.py +0 -0
  28. flexCommunicator/clientLibraries/flcpy/logging/LoggingAndTracking.py +159 -0
  29. flexCommunicator/clientLibraries/flcpy/logging/__init__.py +0 -0
  30. flexCommunicator/clientLibraries/flcpy/messages/StandardizedMessages.py +64 -0
  31. flexCommunicator/clientLibraries/flcpy/messages/__init__.py +0 -0
  32. flexCommunicator/clientLibraries/flcpy/node.py +486 -0
  33. flexCommunicator/clientLibraries/flcpy/security/__init__.py +0 -0
  34. flexCommunicator/clientLibraries/flcpy/security/security.py +189 -0
  35. flexCommunicator/clientLibraries/flcpy/service/__init__.py +0 -0
  36. flexCommunicator/clientLibraries/flcpy/service/client.py +58 -0
  37. flexCommunicator/clientLibraries/flcpy/service/service.py +67 -0
  38. flexCommunicator/clientLibraries/flcpy/transport/PropertyBinding.py +108 -0
  39. flexCommunicator/clientLibraries/flcpy/transport/PropertyRegistry.py +142 -0
  40. flexCommunicator/clientLibraries/flcpy/transport/__init__.py +0 -0
  41. flexCommunicator/clientLibraries/flcpy/utils/Timer.py +22 -0
  42. flexCommunicator/clientLibraries/flcpy/utils/__init__.py +0 -0
  43. flexCommunicator/clientLibraries/flcpy/utils/auxiliary.py +261 -0
  44. flexCommunicator/clientLibraries/flcpy/utils/constants.py +276 -0
  45. flexCommunicator/clientLibraries/flcpy/utils/decorators.py +70 -0
  46. flexCommunicator/clientLibraries/flcpy/validityFrame/__init__.py +0 -0
  47. flexCommunicator/clientLibraries/flcpy/validityFrame/runtimeMonitor.py +101 -0
  48. flexCommunicator/clientLibraries/flcpy/validityFrame/validityFrameManager.py +958 -0
  49. flexcommunicator-0.1.0.dist-info/METADATA +568 -0
  50. flexcommunicator-0.1.0.dist-info/RECORD +53 -0
  51. flexcommunicator-0.1.0.dist-info/WHEEL +5 -0
  52. flexcommunicator-0.1.0.dist-info/licenses/LICENSE +201 -0
  53. flexcommunicator-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,892 @@
1
+ #**************************************************************************
2
+ # * Copyright (C) 2025-present Bert Van Acker (B.MKR) <bva.bmkr@gmail.com>
3
+ # *
4
+ # * This file is part of the flexIA project.
5
+ # *
6
+ # * flexIA can not be copied and/or distributed without the express
7
+ # * permission of Bert Van Acker
8
+ # *************************************************************************
9
+ import yaml
10
+ import jsonpickle
11
+ import jsonpickle.ext.numpy as jsonpickle_numpy
12
+ # 2. Register the NumPy handlers
13
+ jsonpickle_numpy.register_handlers()
14
+
15
+ import logging
16
+ from datetime import datetime
17
+
18
+ from flexCommunicator.clientLibraries.flcpy.commands.composer import compose_command
19
+ # OWN MODULES
20
+ from flexCommunicator.clientLibraries.flcpy.communication.CommunicationManager import CommunicationManager
21
+ from flexCommunicator.clientLibraries.flcpy.knowledge.KnowledgeManager import KnowledgeManager
22
+ from flexCommunicator.clientLibraries.flcpy.logging.LoggingAndTracking import LoggingAndTrackingHandler
23
+ from flexCommunicator.clientLibraries.flcpy.flexCloud.flexCloudManager import flexCloudManager
24
+ from flexCommunicator.clientLibraries.flcpy.utils.Timer import Timer
25
+ from flexCommunicator.clientLibraries.flcpy.flexCloud.flexCloudVariable import flexCloudVariable
26
+ from flexCommunicator.clientLibraries.flcpy.security.security import *
27
+ from flexCommunicator.clientLibraries.flcpy.transport.PropertyRegistry import PropertyRegistry
28
+ from flexCommunicator.clientLibraries.flcpy.utils.constants import *
29
+ from flexCommunicator.clientLibraries.flcpy.commands.commandProcessor import *
30
+ from flexCommunicator.clientLibraries.flcpy.utils.decorators import *
31
+ from flexCommunicator.clientLibraries.flcpy.service.client import client
32
+ from flexCommunicator.clientLibraries.flcpy.action.goal_handle import goal_handle
33
+ from flexCommunicator.clientLibraries.flcpy.action.action_client import ation_client
34
+ from flexCommunicator.clientLibraries.flcpy.fmi.fmuNode import *
35
+ from flexCommunicator.clientLibraries.flcpy.validityFrame.validityFrameManager import *
36
+
37
+ #versioning
38
+ from packaging.version import Version
39
+ # ---------------------------------------------------------------------------
40
+ # GLOBAL VARIABLES
41
+ # ---------------------------------------------------------------------------
42
+ GLOBAL_REQ_KEY = None
43
+
44
+
45
+
46
+
47
+
48
+ class flexNode:
49
+ def __init__(self, config,loaded_config=False, communicationMatrix = None,multi_transport=True,version="0.0a1", verbose=False):
50
+ self._verbose = verbose
51
+ self.timers = []
52
+ self.services = []
53
+
54
+ if loaded_config:
55
+ self.config = config
56
+ else:
57
+ self.config = self.load_config(config)
58
+
59
+ # -- initialize application descriptor --
60
+ self.applicationDescriptor = self._initialize_application_descriptor()
61
+
62
+ # -- initialize logger --
63
+ self.logger = self._initialize_logger()
64
+ # -- initialize storage/knowledge -- #ASSUMPTION:always redis of now
65
+ self.knowledge = self._initialize_knowledge()
66
+
67
+ # -- initialize flexCloud manager --
68
+ self.flexCloud_manager = self._initialize_flexCloud_manager() #ASSUMPTION: always redis cloud!
69
+
70
+ # -- REGISTER APPLICATION STATUS AS FLEXCLOUD VARIABLE
71
+ self.application_status = flexCloudVariable(name=FLEXCLOUD_VARIABLES.STATUS,statusVariable=True)
72
+ self.register_flexCloud_variable(self.application_status)
73
+ self.application_status.set(value=APPLICATION_STATUS.INITIALIZING)
74
+
75
+ # -- LOAD COMMUNICATION MATRIX AND INITIALIZE PROPERTY REGISTRY
76
+ self.property_registry = self._initialize_property_registry(communicationMatrixFile=communicationMatrix)
77
+
78
+ # -- LOAD AND INITIALIZE MULTI-TRANSPORT COMMUNICATION MANAGERS
79
+ self._multi_transport = multi_transport
80
+ if multi_transport:
81
+ self._communicationManagers = []
82
+ comm_config = self.config[CONFIG_KEYS.KEY_COMMUNICATION_MANAGERS_CONFIG]
83
+ if comm_config is not None:
84
+ for comm in comm_config:
85
+ transport_protocol = comm['Data_Manager_Config']['protocol']
86
+ comm_manager = self._initialize_communication_manager(transport_protocol=transport_protocol,config=comm)
87
+ self._communicationManagers.append(comm_manager)
88
+ else:
89
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_EMPTY_COMMUNICATION_MANAGERS)
90
+ else:
91
+ self._communicationManagers = []
92
+
93
+ # -- SUBSCRIBE TO COMMANDS FROM FLEXCLOUD --
94
+ self._cmd_key = self._get_flexCloud_command_key()
95
+ self.flexCloud_manager.subscribe(topic=self._cmd_key, callback=self._on_command)
96
+
97
+ # -- SETUP COMMAND PROCESSOR --
98
+ self._command_processor = commandProcessor(name="flexNode_CMD_PROCESSOR",logger=self.logger,verbose=verbose)
99
+
100
+ # -- SETUP REQUIREMENTS LINKING --
101
+ self.requirements_key = self._get_flexCloud_connectorID() + FLEXCLOUD_VARIABLES.APPLICATIONS + self.applicationDescriptor["id"] + FLEXCLOUD_VARIABLES.REQUIREMENTS
102
+
103
+ # -- SETUP APPLICATION PROFILER --
104
+ self.application_profiler_key = self._get_flexCloud_connectorID() + FLEXCLOUD_VARIABLES.APPLICATIONS + self.applicationDescriptor["id"] + FLEXCLOUD_VARIABLES.PROFILING
105
+
106
+ # -- FMI integration --
107
+ self._fmu_list = []
108
+ self._rt_fmu=None
109
+
110
+ # -- application metadata --
111
+ self._name = "tbd"
112
+ self._id = "tbd"
113
+ self._description = "tbd"
114
+ self._version = "tbd"
115
+
116
+ # -- VALIDITY FRAME INTEGRATION --
117
+ self.vf = None # WARNING: ONLY ONE VF PER NODE FOR NOW!
118
+ self.vf_key = self._get_flexCloud_connectorID() + FLEXCLOUD_VARIABLES.APPLICATIONS + self.applicationDescriptor["id"] + FLEXCLOUD_VARIABLES.VALIDITY_FRAME
119
+
120
+
121
+ # --------------------------------------------------------------------------------------
122
+ # - --------------- Application meta-data ----------------------------------------------
123
+ # --------------------------------------------------------------------------------------
124
+ @property
125
+ def name(self):
126
+ return self._name
127
+
128
+ @name.setter
129
+ def name(self,name):
130
+ self._name = name
131
+
132
+ @property
133
+ def ID(self):
134
+ return self._id
135
+
136
+ @ID.setter
137
+ def ID(self, id):
138
+ self._id = id
139
+
140
+ @property
141
+ def description(self):
142
+ return self._description
143
+
144
+ @description.setter
145
+ def description(self, desc):
146
+ self._description = desc
147
+
148
+
149
+ @property
150
+ def version(self):
151
+ return self._version
152
+
153
+ @version.setter
154
+ def version(self,version):
155
+ self._version = Version(version)
156
+
157
+
158
+ def logInit(self):
159
+ self.logger.info(self.name + "instantiated with ID: " + self.ID + " - version [" + str(self.version) + "]")
160
+
161
+ def load_config(self, config_file):
162
+ with open(config_file, 'r') as file:
163
+ return yaml.safe_load(file)
164
+
165
+ def _initialize_property_registry(self,communicationMatrixFile = "communicationMatrix.yaml"):
166
+ """ Initialize the property registry"""
167
+ try:
168
+ pr = PropertyRegistry(config_path=communicationMatrixFile)
169
+ self.logger.info(FLEXCLOUD_MESSAGES.MSG_INITIALIZING_PROPERTY_REGISTRY)
170
+ return pr
171
+ except:
172
+ return None
173
+
174
+ def _initialize_logger(self):
175
+ # Create the main logger.
176
+ logger = logging.getLogger(self.__class__.__name__)
177
+ logger.setLevel(self.config[CONFIG_KEYS.KEY_LOGGER_CONFIG][CONFIG_KEYS.KEY_LOG_LEVEL])
178
+ # Instantiate the custom logging handler.
179
+ custom_handler = LoggingAndTrackingHandler(config=self.config[CONFIG_KEYS.KEY_LOGGER_CONFIG], applicationDescriptor=self.applicationDescriptor)
180
+ formatter = logging.Formatter(self.config[CONFIG_KEYS.KEY_LOGGER_CONFIG][CONFIG_KEYS.KEY_LOG_FORMAT])
181
+ custom_handler.setFormatter(formatter)
182
+ logger.addHandler(custom_handler)
183
+ return logger
184
+
185
+ def _initialize_knowledge(self):
186
+ """Initialize the knowledge object based on the config."""
187
+ self.logger.info(
188
+ FLEXCLOUD_MESSAGES.MSG_INITIALIZING_KNOWLEDGE.format(
189
+ self.config[CONFIG_KEYS.KEY_KNOWLEDGE_CONFIG]["knowledge_type"]
190
+ )
191
+ )
192
+ return KnowledgeManager(self.config[CONFIG_KEYS.KEY_KNOWLEDGE_CONFIG])
193
+
194
+ def _initialize_flexCloud_manager(self):
195
+ """Initialize the Event Manager based on the config."""
196
+ self.logger.info(FLEXCLOUD_MESSAGES.MSG_INITIALIZING_FLEXCLOUD_MANAGER)
197
+ return flexCloudManager(config=self.config[CONFIG_KEYS.KEY_FLEXCLOUD_MANAGER_CONFIG])
198
+
199
+
200
+ def _initialize_application_descriptor(self):
201
+ """Initialize the application description based on the config."""
202
+ return self.config[CONFIG_KEYS.KEY_APPLICATION_DESCRIPTOR]
203
+
204
+ def spin(self):
205
+ """Start the component and enable Event."""
206
+ self.logger.info(FLEXCLOUD_MESSAGES.MSG_STARTING.format(self.__class__.__name__))
207
+ if self._multi_transport:
208
+ for comm in self._communicationManagers:
209
+ comm.start()
210
+ else:
211
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_EMPTY_COMMUNICATION_MANAGERS)
212
+ # activate all auto-start timers
213
+ for timer in self.timers:
214
+ timer.start()
215
+ # activate flexCloud commands
216
+ self.flexCloud_manager.start()
217
+
218
+ def shutdown(self):
219
+
220
+ """Shutdown the component and stop Event."""
221
+ self.logger.info(FLEXCLOUD_MESSAGES.MSG_SHUTTING_DOWN.format(self.__class__.__name__))
222
+ # set application status to stopped
223
+ self.application_status.set(value=APPLICATION_STATUS.STOPPED)
224
+ if self._multi_transport:
225
+ for comm in self._communicationManagers:
226
+ comm.stop()
227
+ # deactivate all auto-start timers
228
+ for timer in self.timers:
229
+ timer.stop()
230
+ # activate flexCloud commands
231
+ self.flexCloud_manager.stop()
232
+
233
+
234
+
235
+ # --------------------------------------------------------------------------------------
236
+ # - --------------- flexCommunicator event helpers -------------------------
237
+ # --------------------------------------------------------------------------------------
238
+ def _publish_event(self, event_key, message=DEFAULT_MESSAGES.DEFAULT_EVENT_MESSAGE):
239
+ """Publish Event using the Event manager."""
240
+ # Generate a unique ID for the message
241
+ message_id = str(uuid.uuid4().hex[:8])
242
+ # Get the current timestamp in ISO 8601 format
243
+ timestamp = datetime.now().isoformat()
244
+
245
+ message['uid'] = message_id
246
+ message['timestamp'] = timestamp
247
+
248
+ #TODO: change to use transport protocol defined to event
249
+ if self.event_manager:
250
+ self.event_manager.publish(event_key, json.dumps(message))
251
+ else:
252
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_EVENT_MANAGER_NOT_SET)
253
+
254
+ # --------------------------------------------------------------------------------------
255
+ # - --------------- flexCommunicator Knowledge/storage helpers -------------------------
256
+ # --------------------------------------------------------------------------------------
257
+
258
+ def _read_knowledge(self, key, queueSize=1,decode=True):
259
+ """Read a value from the knowledge Manager."""
260
+ value = self.knowledge.read(key, queueSize)
261
+ if decode:
262
+ if value is not None:
263
+ value = value.decode(ENCODING.ENCODING_UTF8) # Convert bytes to string
264
+ try:
265
+ value = json.loads(value) # Try to deserialize the value if it's a JSON string
266
+ except json.JSONDecodeError:
267
+ pass # If it's not JSON, return it as a string
268
+ return value
269
+
270
+ def _write_knowledge(self, key, value,history=False):
271
+ """Write a value to the knowledge Manager."""
272
+ success=False
273
+ if isinstance(key, str):
274
+ if isinstance(value, str):
275
+ value = str(value)
276
+ if history:
277
+ success = self.knowledge.write_history(key, value)
278
+ else:
279
+ success=self.knowledge.write(key, value)
280
+ return success
281
+ else:
282
+ # Convert the class instance to a dictionary
283
+ class_dict = {}
284
+ for key_attr, attr_value in key.__dict__.items():
285
+ public_key = key_attr.lstrip('_')
286
+ class_dict[public_key] = attr_value
287
+ value = json.dumps(class_dict) # Serialize the dictionary to a JSON string
288
+ if history:
289
+ success = self.knowledge.write_history(key, value)
290
+ else:
291
+ success = self.knowledge.write(key, value)
292
+ return success
293
+
294
+ # --------------------------------------------------------------------------------------
295
+ # - ------------------------- flexCommunicator helpers ---------------------------------
296
+ # --------------------------------------------------------------------------------------
297
+ #TODO: move to auxiliary??
298
+ def _encode(self, message):
299
+ return jsonpickle.encode(message)
300
+
301
+
302
+ def _initialize_communication_manager(self,config=None,transport_protocol=''):
303
+ """Initialize a generic Communication Manager based on the config."""
304
+ self.logger.info(FLEXCLOUD_MESSAGES.MSG_INITIALIZING_IO_MANAGER + " - TYPE: "+transport_protocol)
305
+ return CommunicationManager(config=config[CONFIG_KEYS.KEY_DATA_MANAGER_CONFIG])
306
+
307
+ def _determine_communication_manager(self,transport_protocol):
308
+ _communication_manager = None
309
+ for comm in self._communicationManagers:
310
+ if comm.protocol == transport_protocol:
311
+ _communication_manager= comm
312
+
313
+ if _communication_manager == None:
314
+ self.logger.error(FLEXCLOUD_ERRORS.ERR_COMMUNICATION_MANAGER_NOT_FOUND)
315
+ return _communication_manager
316
+
317
+ def _check_available_communication_manager(self,transport_protocol):
318
+ available = False
319
+ for comm in self._communicationManagers:
320
+ if comm.protocol == transport_protocol:
321
+ available = True
322
+ break
323
+ if available is False:
324
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_NO_MATCHING_TRANSPORT_PROTOCOL + transport_protocol)
325
+ return available
326
+
327
+
328
+
329
+ # --------------------------------------------------------------------------------------
330
+ # --------------------------------------------------------------------------------------
331
+ # flexCommunicator API
332
+ # --------------------------------------------------------------------------------------
333
+ # --------------------------------------------------------------------------------------
334
+
335
+ # --------------------------------------------------------------------------------------
336
+ # - --------------------------------TIMERS --------------------------------------------
337
+ # --------------------------------------------------------------------------------------
338
+ def create_timer(self,timer_period=0.1,callback=None, autostart = True):
339
+ """Creates a timer for periodic execution of functions"""
340
+ timer = Timer(timer_period, callback)
341
+ if autostart:
342
+ self.timers.append(timer)
343
+ return timer
344
+
345
+
346
+ #--------------------------------------------------------------------------------------
347
+ #-------------------------------DATA HANDLING -----------------------------------------
348
+ #--------------------------------------------------------------------------------------
349
+ def set_data(self, data_name="", data=DEFAULT_MESSAGES.DEFAULT_DATA_MESSAGE):
350
+ """Publish or emit data as a message."""
351
+
352
+ #--------------- MULTI-TRANSPORT------------------------
353
+ if self._multi_transport:
354
+ # DETERMINE COMMUNCATION MANAGER FOR GIVEN DATA
355
+ protocol = self.property_registry.get_transport(name=data_name)
356
+ _comm_manager = self._determine_communication_manager(transport_protocol=protocol)
357
+ # DISABLE ENCODING FOR OPC-UA
358
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_OPC_UA or protocol == TRANSPORT_PROTOCOLS.PROTOCOL_TWINCAT_ADS:
359
+ message_encoded = data
360
+ # DISABLE ENCODING FOR STRING type
361
+ elif isinstance(data, str):
362
+ message_encoded = data
363
+ else:
364
+ message_encoded = self._encode(data)
365
+
366
+ if _comm_manager:
367
+ try:
368
+ # determing transport binding for given data property
369
+ transport = self.property_registry.get_binding(name=data_name, transport=protocol)
370
+ _comm_manager.publish_transport(transport=transport, message=message_encoded)
371
+ except:
372
+ self.logger.error(FLEXCLOUD_ERRORS.ERR_PROPERTY_REGISTRY_NOT_FOUND)
373
+ else:
374
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_DATA_MANAGER_NOT_SET)
375
+
376
+ # --------------- SINGLE-TRANSPORT USING DATA_MANAGER------------------------
377
+ else:
378
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_SINGLE_COMMUNICATION_MANAGER_DEPRICATED)
379
+
380
+
381
+ def get_data(self, data_name=""):
382
+ """Retrieve data."""
383
+ # --------------- MULTI-TRANSPORT------------------------
384
+ if self._multi_transport:
385
+ # DETERMINE COMMUNCATION MANAGER FOR GIVEN DATA
386
+ protocol = self.property_registry.get_transport(name=data_name)
387
+ _comm_manager = self._determine_communication_manager(transport_protocol=protocol)
388
+ if _comm_manager:
389
+ try:
390
+ # determing transport binding for given data property
391
+ transport = self.property_registry.get_binding(name=data_name,transport=_comm_manager.protocol)
392
+ return _comm_manager.read_transport(transport=transport)
393
+ except:
394
+ self.logger.error(FLEXCLOUD_ERRORS.ERR_PROPERTY_REGISTRY_NOT_FOUND)
395
+ return -1
396
+ else:
397
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_DATA_MANAGER_NOT_SET)
398
+ return -1
399
+ # --------------- SINGLE-TRANSPORT USING DATA_MANAGER------------------------
400
+ else:
401
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_SINGLE_COMMUNICATION_MANAGER_DEPRICATED)
402
+ return -1
403
+
404
+ def register_data_callback(self, data_name, callback,isService=False):
405
+ """Register a callback for Data manager events (MQTT or Redis)."""
406
+ # --------------- MULTI-TRANSPORT------------------------
407
+ if self._multi_transport:
408
+ # DETERMINE COMMUNCATION MANAGER FOR GIVEN DATA
409
+ protocol = self.property_registry.get_transport(name=data_name)
410
+ _comm_manager = self._determine_communication_manager(transport_protocol=protocol)
411
+ if _comm_manager:
412
+ transport = self.property_registry.get_binding(name=data_name, transport=_comm_manager.protocol)
413
+ _comm_manager.subscribe_transport(transport=transport, callback=callback,isService=isService)
414
+ self.logger.info(FLEXCLOUD_MESSAGES.MSG_REGISTER_EVENT_CALLBACK.format(data_name))
415
+ else:
416
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_REGISTER_EVENT_CALLBACK_NOT_SET)
417
+
418
+ # --------------- SINGLE-TRANSPORT USING DATA_MANAGER------------------------
419
+ else:
420
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_SINGLE_COMMUNICATION_MANAGER_DEPRICATED)
421
+
422
+
423
+ # --------------------------------------------------------------------------------------
424
+ # -------------------------------DATA STORAGE HANDLING -----------------------------------------
425
+ # --------------------------------------------------------------------------------------
426
+ def store_data(self, data_name="", data=DEFAULT_MESSAGES.DEFAULT_DATA_MESSAGE,history=False):
427
+ """Store data as a message in a knowledge service."""
428
+ message_encoded = self._encode(data)
429
+ if self.knowledge:
430
+ try:
431
+ # determing transport binding for given data property
432
+ data_key = self.property_registry.get_binding(name=data_name, transport=self.knowledge.backend).generic_endpoint
433
+ self._write_knowledge(key=data_key,value=message_encoded,history=history)
434
+ except:
435
+ self.logger.error(FLEXCLOUD_ERRORS.ERR_PROPERTY_REGISTRY_NOT_FOUND)
436
+ else:
437
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_KNOWLEDGE_MANAGER_NOT_SET)
438
+
439
+ def retrieve_data(self, data_name):
440
+ """Retrieve data stored as a message in a knowledge service."""
441
+ if self.knowledge:
442
+ try:
443
+ # determing transport binding for given data property
444
+ data_key = self.property_registry.get_binding(name=data_name, transport=self.knowledge.backend).generic_endpoint
445
+ data = self._read_knowledge(key=data_key,decode=False)
446
+ except:
447
+ data = None
448
+ self.logger.error(FLEXCLOUD_ERRORS.ERR_PROPERTY_REGISTRY_NOT_FOUND)
449
+ else:
450
+ data = None
451
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_KNOWLEDGE_MANAGER_NOT_SET)
452
+ return data
453
+
454
+ def delete_data(self, data_key, data=DEFAULT_MESSAGES.DEFAULT_DATA_MESSAGE):
455
+ """Delete data stored as a message in a knowledge service."""
456
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_NOT_IMPLEMENTED)
457
+
458
+ def exists_data(self, data_key, data=DEFAULT_MESSAGES.DEFAULT_DATA_MESSAGE):
459
+ """Check if data exists in a knowledge service."""
460
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_NOT_IMPLEMENTED)
461
+
462
+
463
+
464
+ # --------------------------------------------------------------------------------------
465
+ # - -----------------------------EVENT HANDLING -----------------------------------------
466
+ # --------------------------------------------------------------------------------------
467
+ def emit_event(self, event_key, data=DEFAULT_MESSAGES.DEFAULT_EVENT_MESSAGE):
468
+ """Publish or emit event as a message."""
469
+ message_encoded = self._encode(data)
470
+ # TODO: change to use transport protocol defined to event
471
+ if self.event_manager:
472
+ self.event_manager.publish(event_key, message_encoded)
473
+ else:
474
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_EVENT_MANAGER_NOT_SET)
475
+ def register_event_callback(self, event_key, callback):
476
+ """Register a callback for Event manager events (MQTT or Redis)."""
477
+ #TODO: change to use transport protocol defined to event
478
+ if self.event_manager:
479
+ self.event_manager.subscribe(event_key, callback)
480
+ self.logger.info(FLEXCLOUD_MESSAGES.MSG_REGISTER_EVENT_CALLBACK.format(event_key))
481
+ else:
482
+ self.logger.warning(FLEXCLOUD_WARNINGS.WARN_REGISTER_EVENT_CALLBACK_NOT_SET)
483
+
484
+ # --------------------------------------------------------------------------------------
485
+ # - -----------------------flexCloud variable handling ---------------------------------
486
+ # --------------------------------------------------------------------------------------
487
+ def register_flexCloud_variable(self,variable=None):
488
+ """Register a flexCloud variable."""
489
+ # assign flexCloud manager + assign flexCloud connectorID
490
+ variable.flexCloud_manager = self.flexCloud_manager
491
+ variable.flexCloud_connectorID = self._get_flexCloud_connectorID()
492
+ variable.flexCloud_applicationID = self.applicationDescriptor["id"]
493
+ variable.initialize()
494
+ self.logger.info("flexCloud variable registered, name:" + variable._name)
495
+
496
+ def _get_flexCloud_connectorID(self):
497
+
498
+ # TODO: determine the connectorID, either static or dynamic via OS variables
499
+ if self.applicationDescriptor["connectorID_type"] == "static":
500
+ flexCloud_connectorID = self.applicationDescriptor["connectorID"]
501
+ else:
502
+ flexCloud_connectorID = get_os_variable(SECURITY_STORE.FLEXCLOUD_CONNECTORID_SECRET)
503
+
504
+ return flexCloud_connectorID
505
+
506
+ def _get_flexCloud_command_key(self):
507
+
508
+ if self.applicationDescriptor["connectorID_type"] == "static":
509
+ flexCloud_connectorID = self.applicationDescriptor.get("connectorID","default@default-1aa111")
510
+ else:
511
+ flexCloud_connectorID = get_os_variable(SECURITY_STORE.FLEXCLOUD_CONNECTORID_SECRET)
512
+
513
+ flexCloud_application_id = self.applicationDescriptor.get("id", "default")
514
+ flexCloud_command_key = flexCloud_connectorID+":"+flexCloud_application_id+FLEXCLOUD_KEYS.COMMAND
515
+
516
+ return flexCloud_command_key
517
+
518
+ def _get_flexCloud_command_response_key(self):
519
+
520
+ if self.applicationDescriptor["connectorID_type"] == "static":
521
+ flexCloud_connectorID = self.applicationDescriptor.get("connectorID","default@default-1aa111")
522
+ else:
523
+ flexCloud_connectorID = get_os_variable(SECURITY_STORE.FLEXCLOUD_CONNECTORID_SECRET)
524
+
525
+ flexCloud_application_id = self.applicationDescriptor.get("id", "default")
526
+ flexCloud_command_key = flexCloud_connectorID+":"+flexCloud_application_id+FLEXCLOUD_KEYS.RESPONSE
527
+
528
+ return flexCloud_command_key
529
+
530
+ # --------------------------------------------------------------------------------------
531
+ # - -----------------------flexCloud command handling ---------------------------------
532
+ # --------------------------------------------------------------------------------------
533
+ def _on_command(self,message):
534
+ """Process the incoming commands"""
535
+ #determine command action and data
536
+ signature_valid,action,data = self._command_processor.process_command(message)
537
+
538
+ if signature_valid:
539
+ if action == FLEXCLOUD_COMMANDS.PING:
540
+ #PUBLISH APPLICATION STATUS
541
+ params = {"applicationName": self.applicationDescriptor.get("id","default"),"applicationStatus": self.application_status.get()}
542
+ response = compose_command(FLEXCLOUD_COMMANDS.PONG, params, priority=2, expiry_seconds=120)
543
+ response_key = self._get_flexCloud_command_response_key()
544
+ self.flexCloud_manager.publish(key=response_key,value=response)
545
+
546
+ elif action == FLEXCLOUD_COMMANDS.UPDATE_TRANSPORT:
547
+ # CHANGE PROPERTY PROTOCOL
548
+ changeAllowed = self._check_available_communication_manager(transport_protocol=data["protocol"])
549
+ updated = False
550
+ # CHECK IF ALLOWED TO CHANGE PROTOCOL (IF PROTOCOL CLIENT IS AVAILABLE)
551
+ if changeAllowed:
552
+ updated = self.property_registry.update_transport(name=data["propertyName"],transport=data["protocol"])
553
+
554
+ if updated:
555
+ params = {"propertyName": data["propertyName"],"protocol": self.property_registry.get_transport(data["propertyName"]),"status":"Succeeded"}
556
+ response = compose_command(FLEXCLOUD_COMMANDS.UPDATE_TRANSPORT, params, priority=2, expiry_seconds=120)
557
+ self.logger.info(FLEXCLOUD_MESSAGES.MSG_UPDATE_PROPERTY_PROTOCOL_SUCCESS + data["propertyName"]+ " - new protocol: "+self.property_registry.get_transport(data["propertyName"]))
558
+ else:
559
+ params = {"propertyName": data["propertyName"],"protocol": self.property_registry.get_transport(data["propertyName"]),"status":"Failed"}
560
+ response = compose_command(FLEXCLOUD_COMMANDS.UPDATE_TRANSPORT, params, priority=2,expiry_seconds=120)
561
+ self.logger.info(FLEXCLOUD_ERRORS.ERR_UPDATE_PROPERTY_PROTOCOL_FAIL + data["propertyName"]+" - requested protocol: " +data["protocol"])
562
+
563
+ response_key = self._get_flexCloud_command_response_key()
564
+ self.flexCloud_manager.publish(key=response_key, value=response)
565
+
566
+ return True
567
+ else:
568
+ self.logger.error(FLEXCLOUD_ERRORS.ERR_INVALID_COMMAND_SIGNATURE)
569
+ return False
570
+
571
+ # --------------------------------------------------------------------------------------
572
+ # - -------------------Contract-based component functions ------------------------------
573
+ # --------------------------------------------------------------------------------------
574
+ def register_contract_variable(self,variable=None):
575
+ """Register a flexCloud variable."""
576
+ # assign flexCloud manager + assign flexCloud connectorID
577
+ variable.flexCloud_manager = self.flexCloud_manager
578
+ variable.flexCloud_connectorID = self._get_flexCloud_connectorID()
579
+ variable.flexCloud_applicationID = self.applicationDescriptor["id"]
580
+ variable.initialize()
581
+ self.logger.info("Contract variable registered, name:" + variable._name)
582
+
583
+ # --------------------------------------------------------------------------------------
584
+ # - -------------------Validity Frame component functions ------------------------------
585
+ # --------------------------------------------------------------------------------------
586
+ def register_runtime_monitor(self, monitor=None):
587
+ """Register a VF runtime monitor."""
588
+ # assign flexCloud manager + assign flexCloud connectorID
589
+ monitor.flexCloud_manager = self.flexCloud_manager
590
+ monitor.flexCloud_connectorID = self._get_flexCloud_connectorID()
591
+ monitor.flexCloud_applicationID = self.applicationDescriptor["id"]
592
+ monitor.initialize()
593
+ self.logger.info("VF runtime monitor registered, name:" + monitor._name)
594
+
595
+ # --------------------------------------------------------------------------------------
596
+ # - --------------------------------SERVICES --------------------------------------------
597
+ # --------------------------------------------------------------------------------------
598
+ def create_service(self, protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,service=None,serviceName='TBD', callback=None):
599
+ """Creates and registers a new service."""
600
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_MQTT or protocol == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS:
601
+ #register service to node
602
+
603
+ if serviceName is not None:
604
+ #add property to and resolve name
605
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_MQTT:
606
+ self.property_registry.add_property(name=serviceName+"_request",description="Request property for service <"+serviceName+">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,mqtt="/"+serviceName+"/_request")
607
+ self.property_registry.add_property(name=serviceName + "_response",description="Response property for service <" + serviceName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT, mqtt="/" + serviceName + "/_response")
608
+ self.register_data_callback(data_name=serviceName+"_request",callback=self.generic_service_callback,isService=True)
609
+ elif protocol == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS:
610
+ self.property_registry.add_property(name=serviceName + "_request",description="Request property for service <" + serviceName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis= serviceName + ":_request")
611
+ self.property_registry.add_property(name=serviceName + "_response",description="Response property for service <" + serviceName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis= serviceName + ":_response")
612
+ self.register_data_callback(data_name=serviceName + "_request",callback=self.generic_service_callback,isService=True)
613
+ if service is not None:
614
+ if callback is not None:
615
+ service.requestProperty = serviceName + "_request"
616
+ service.responseProperty= serviceName + "_response"
617
+ service.callback = callback
618
+ self.services.append(service)
619
+ else:
620
+ self.logger.info("No service name provided.")
621
+ else:
622
+ self.logger.info("Selected transport protocol <"+protocol+"> not supported for flexNode service.")
623
+
624
+
625
+ def generic_service_callback(self,topic,request):
626
+ "Generic service callback"
627
+
628
+ _service = None
629
+ _request = None
630
+ _response = None
631
+ #1. determine service
632
+ for service in self.services:
633
+ if service.name in topic:
634
+ _service = service
635
+ #instantiate request
636
+ req = service.request
637
+ req.instantiate(json_str=request)
638
+ _request = req
639
+ #instantiate response
640
+ resp = service.response
641
+ _response = resp
642
+
643
+ #execute callback
644
+ response = _service.callback(_request,_response)
645
+
646
+ #publish response
647
+ self.set_data(data_name=_service.responseProperty, data=response)
648
+
649
+
650
+ def create_client(self, protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,service=None,serviceName='TBD'):
651
+ """Creates and registers a new service."""
652
+ _comm_manager = None
653
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_MQTT or protocol == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS:
654
+ if serviceName is not None:
655
+ _client = client(name=serviceName+"_client",logger=self.logger,set_data=self.set_data)
656
+ # add property to and resolve name
657
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_MQTT:
658
+ self.property_registry.add_property(name=serviceName + "_request",description="Request property for service <" + serviceName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT, mqtt="/" + serviceName + "/_request")
659
+ self.property_registry.add_property(name=serviceName + "_response",description="Response property for service <" + serviceName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,mqtt="/" + serviceName + "/_response")
660
+ self.register_data_callback(data_name=serviceName + "_response",callback=_client.response_callback, isService=True)
661
+ elif protocol == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS:
662
+ self.property_registry.add_property(name=serviceName + "_request",description="Request property for service <" + serviceName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis=serviceName + ":_request")
663
+ self.property_registry.add_property(name=serviceName + "_response",description="Response property for service <" + serviceName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis=serviceName + ":_response")
664
+ self.register_data_callback(data_name=serviceName + "_response",callback=_client.response_callback, isService=True)
665
+ if service is not None:
666
+ service.requestProperty = serviceName + "_request"
667
+ service.responseProperty = serviceName + "_response"
668
+ self.services.append(service)
669
+
670
+ #add client service
671
+ _client.service = service
672
+ return _client
673
+ else:
674
+ self.logger.info("No service name provided.")
675
+ else:
676
+ self.logger.info("Selected transport protocol <"+protocol+"> not supported for flexNode service.")
677
+
678
+ # --------------------------------------------------------------------------------------
679
+ # - --------------------------------ACTIONS --------------------------------------------
680
+ # --------------------------------------------------------------------------------------
681
+ def create_action_server(self, protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,action=None,actionName='TBD', callback=None):
682
+ """Creates and registers a new action."""
683
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_MQTT or protocol == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS:
684
+ #register service to node
685
+
686
+ if actionName is not None:
687
+ #add property to and resolve name
688
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_MQTT:
689
+ self.property_registry.add_property(name=actionName+"_request",description="Request property for action <"+actionName+">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,mqtt="/"+actionName+"/_request")
690
+ self.property_registry.add_property(name=actionName + "_response",description="Response property for action <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT, mqtt="/" + actionName + "/_response")
691
+ self.property_registry.add_property(name=actionName + "_feedback",description="Feedback property for action <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,mqtt="/" + actionName + "/_feedback")
692
+
693
+ self.register_data_callback(data_name=actionName+"_request",callback=self.generic_action_callback,isService=True)
694
+ elif protocol == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS:
695
+ self.property_registry.add_property(name=actionName + "_request",description="Request property for action <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis=actionName + ":_request")
696
+ self.property_registry.add_property(name=actionName + "_response",description="Response property for action <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis=actionName + ":_response")
697
+ self.property_registry.add_property(name=actionName + "_feedback",description="Feedback property for action <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis=actionName + ":_feedback")
698
+
699
+ self.register_data_callback(data_name=actionName + "_request",callback=self.generic_action_callback,isService=True)
700
+ if action is not None:
701
+ if callback is not None:
702
+ action.requestProperty = actionName + "_request"
703
+ action.responseProperty= actionName + "_response"
704
+ action.feedbackProperty = actionName + "_feedback"
705
+ action.callback = callback
706
+ self.services.append(action)
707
+ else:
708
+ self.logger.info("No action name provided.")
709
+ else:
710
+ self.logger.info("Selected transport protocol <"+protocol+"> not supported for flexNode action.")
711
+
712
+
713
+ def generic_action_callback(self,topic,request):
714
+ "Generic service callback"
715
+
716
+ _action = None
717
+ _request = None
718
+ _response = None
719
+ _feedback = None
720
+ #1. determine service
721
+ for action in self.services:
722
+ if action.name in topic:
723
+ _action = action
724
+ #instantiate request
725
+ _action.request.instantiate(json_str=request)
726
+
727
+ # instantiate the goal_handle with asynchronous communication manager #TOOD: CHECK IF THERE IS A BETTER WAY
728
+
729
+ _goal_handle = goal_handle(name=_action.name+"_goal_handle",action=_action,logger=self.logger)
730
+
731
+ #execute callback
732
+ goal_handle_result = _action.callback(_goal_handle)
733
+
734
+
735
+ #publish response
736
+ self.set_data(data_name=_action.responseProperty, data=goal_handle_result.response)
737
+
738
+ def publish_action_feedback(self,goal_handle):
739
+ self.set_data(data_name=goal_handle.action.feedbackProperty, data=goal_handle.action.feedback)
740
+
741
+
742
+
743
+ def publish_action_success(self,goal_handle):
744
+ goal_handle.feedback.status = GOAL_STATUS.SUCCEEDED
745
+ self.set_data(data_name=goal_handle.action.feedbackProperty, data=goal_handle.action.feedback)
746
+
747
+
748
+ def create_action_client(self, protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,action=None,actionName='TBD'):
749
+ """Creates and registers a new service."""
750
+ _comm_manager = None
751
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_MQTT or protocol == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS:
752
+ if actionName is not None:
753
+ _client = ation_client(name=actionName+"_client",logger=self.logger,set_data=self.set_data)
754
+ # add property to and resolve name
755
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_MQTT:
756
+ self.property_registry.add_property(name=actionName + "_request",description="Request property for service <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT, mqtt="/" + actionName + "/_request")
757
+ self.property_registry.add_property(name=actionName + "_feedback",description="Feedback property for service <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,mqtt="/" + actionName + "/_feedback")
758
+ self.property_registry.add_property(name=actionName + "_response",description="Response property for service <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,mqtt="/" + actionName + "/_response")
759
+ self.register_data_callback(data_name=actionName + "_response",callback=_client.response_callback, isService=True)
760
+ self.register_data_callback(data_name=actionName + "_feedback", callback=_client.feedback_callback,isService=True)
761
+ elif protocol == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS:
762
+ self.property_registry.add_property(name=actionName + "_request",description="Request property for service <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis=actionName + ":_request")
763
+ self.property_registry.add_property(name=actionName + "_feedback",description="Feedback property for service <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis=actionName + ":_feedback")
764
+ self.property_registry.add_property(name=actionName + "_response",description="Response property for service <" + actionName + ">",protocol=TRANSPORT_PROTOCOLS.PROTOCOL_REDIS,redis=actionName + ":_response")
765
+ self.register_data_callback(data_name=actionName + "_response",callback=_client.response_callback, isService=True)
766
+ self.register_data_callback(data_name=actionName + "_feedback", callback=_client.feedback_callback,isService=True)
767
+ if action is not None:
768
+ action.requestProperty = actionName + "_request"
769
+ action.responseProperty = actionName + "_response"
770
+ action.feedbackProperty = actionName + "_feedback"
771
+ self.services.append(action)
772
+
773
+ #add client service
774
+ _client.action = action
775
+ return _client
776
+ else:
777
+ self.logger.info("No action name provided.")
778
+ else:
779
+ self.logger.info("Selected transport protocol <"+protocol+"> not supported for flexNode action.")
780
+
781
+ # --------------------------------------------------------------------------------------
782
+ # - -----------------------------FMI integration ---------------------------------------
783
+ # --------------------------------------------------------------------------------------
784
+ def create_fmu_component(self,name='tbd',description="no description provided",stepsize=0.01,fmuPath="/input"):
785
+ _fmu = None
786
+ try:
787
+ #TODO: load all ports automatically!
788
+ _fmu = FMUNode(name=name,description=description,stepsize=stepsize,fmuPath=fmuPath)
789
+ self._fmu_list.append(_fmu)
790
+ except:
791
+ self.logger.error("Could not create FMU components.")
792
+
793
+ return _fmu
794
+
795
+ def create_standalone_rt_fmu(self,protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,name='tbd',description="no description provided",stepsize=0.01,fmuPath="/input"):
796
+ """
797
+ Generate a real-time standalone FMU (Functional Mock-up Unit).
798
+
799
+ Notes:
800
+ - only ONE real-time FMU can be instantiated per node
801
+ """
802
+ _fmu = None
803
+ try:
804
+ self._rt_fmu = FMUNode(name=name, description=description, stepsize=stepsize, fmuPath=fmuPath)
805
+
806
+ # 1. instantiate timer for auto-triggering
807
+ _timer = self.create_timer(timer_period=stepsize,callback=self._rt_fmu_dostep_callback)
808
+ # 2. instantiate properties for all input/outputs
809
+ for input in self._rt_fmu.getInputs():
810
+ self._add_generated_property(protocol=protocol,name=input[0],description="Auto-generated property for FMU input <"+input[0]+"> - value reference ["+input[1].__str__()+"]",prefix=self._rt_fmu.name,key=input[0])
811
+ for output in self._rt_fmu.getOutputs():
812
+ self._add_generated_property(protocol=protocol,name=output[0],description="Auto-generated property for FMU output <"+output[0]+"> - value reference ["+output[1].__str__()+"]",prefix=self._rt_fmu.name,key=output[0])
813
+ # 3. expose all parameters to cloud
814
+
815
+ except:
816
+ self.logger.error("Could not create real-time FMU components.")
817
+
818
+ return self._rt_fmu
819
+
820
+ def _rt_fmu_dostep_callback(self):
821
+ try:
822
+
823
+ # perform doStep function
824
+ self._rt_fmu.doStep()
825
+ # expose output ports
826
+ #TODO:add expose ports
827
+ except:
828
+ self.logger.error("Failed to perform the rt_fmu_callback.")
829
+
830
+
831
+ # --------------------------------------------------------------------------------------
832
+ # - -----------------------------HELPERS ---------------------------------------
833
+ # --------------------------------------------------------------------------------------
834
+ def _add_generated_property(self,protocol=TRANSPORT_PROTOCOLS.PROTOCOL_MQTT,name="",description="",prefix=None,key="in"):
835
+
836
+ if protocol == TRANSPORT_PROTOCOLS.PROTOCOL_MQTT:
837
+ if prefix is not None:
838
+ _key = prefix+"/"+key
839
+ else:
840
+ _key = key
841
+ self.property_registry.add_property(name=name,description=description,protocol=protocol,mqtt=_key)
842
+ elif protocol == TRANSPORT_PROTOCOLS.PROTOCOL_REDIS:
843
+ if prefix is not None:
844
+ _key = prefix+":"+key
845
+ else:
846
+ _key = key
847
+ self.property_registry.add_property(name=name, description=description, protocol=protocol, redis=_key)
848
+ else:
849
+ self.logger.info("Selected transport protocol <" + protocol + "> not supported for auto-generated properties.")
850
+
851
+ def update_property_endpoint(self,property_name="",endpoint=""):
852
+ isUpdated = self.property_registry.update_endpoint(name=property_name,endpoint=endpoint)
853
+ if isUpdated:
854
+ self.logger.info("Property <"+property_name+"> endpoint updated to: "+endpoint)
855
+ else:
856
+ self.logger.warning("Failed to update endpoint for property <"+property_name+"> to: "+endpoint)
857
+
858
+ # --------------------------------------------------------------------------------------
859
+ # - -----------------------------VF integration ---------------------------------------
860
+ # --------------------------------------------------------------------------------------
861
+ def loadVF(self,path_to_vf="input/VF.vf",verbose=False):
862
+ # 1. load VF
863
+ self.vf = validityFrameManager(name="VFManager", logger=self.logger)
864
+ self.vf.load(path_to_vf=path_to_vf)
865
+ # 2. dump VF if verbose
866
+ if verbose: self.vf.dump()
867
+ # 3. Expose static parts
868
+ self._exposeArchitecture()
869
+ self._exposeATraceabilityGraph()
870
+ # 4. register runmonitors
871
+ self._registerRuntimeMonitors()
872
+
873
+
874
+ return self.vf
875
+
876
+ def update_vf_monitor(self,monitor_name="",value=0.0):
877
+ x=1
878
+ #TODO: update runtime monitor by name
879
+
880
+ def _exposeArchitecture(self):
881
+ _architecture = self.vf.generateSAIfetyArchitecture()
882
+ _key = self.vf_key + VALIDITY_FRAME_INTEGRATION.ARCHITECTURE
883
+ self.flexCloud_manager.write(_key, self._encode(_architecture))
884
+
885
+ def _exposeATraceabilityGraph(self):
886
+ _traceabilityGraph = self.vf.generateTraceabilityGraph()
887
+ _key = self.vf_key + VALIDITY_FRAME_INTEGRATION.TRACEABILITYGRAPH
888
+ self.flexCloud_manager.write(_key, self._encode(_traceabilityGraph))
889
+
890
+ def _registerRuntimeMonitors(self):
891
+ for monitor in self.vf.flexia_runtime_monitors:
892
+ self.register_runtime_monitor(monitor=monitor)