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.
- flexCommunicator/__init__.py +0 -0
- flexCommunicator/clientLibraries/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/action/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/action/action.py +85 -0
- flexCommunicator/clientLibraries/flcpy/action/action_client.py +70 -0
- flexCommunicator/clientLibraries/flcpy/action/goal_handle.py +50 -0
- flexCommunicator/clientLibraries/flcpy/codec/ROS2Codec.py +227 -0
- flexCommunicator/clientLibraries/flcpy/codec/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/commands/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/commands/commandProcessor.py +113 -0
- flexCommunicator/clientLibraries/flcpy/commands/composer.py +25 -0
- flexCommunicator/clientLibraries/flcpy/communication/CommunicationManager.py +579 -0
- flexCommunicator/clientLibraries/flcpy/communication/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/contracts/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/contracts/contractVariable.py +93 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudConfiguration.py +16 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudManager.py +162 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudOrchestrator.py +248 -0
- flexCommunicator/clientLibraries/flcpy/flexCloud/flexCloudVariable.py +130 -0
- flexCommunicator/clientLibraries/flcpy/flexNode.py +892 -0
- flexCommunicator/clientLibraries/flcpy/fmi/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/fmi/fmuNode.py +166 -0
- flexCommunicator/clientLibraries/flcpy/fmi/node.py +360 -0
- flexCommunicator/clientLibraries/flcpy/knowledge/KnowledgeManager.py +288 -0
- flexCommunicator/clientLibraries/flcpy/knowledge/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/logging/LoggingAndTracking.py +159 -0
- flexCommunicator/clientLibraries/flcpy/logging/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/messages/StandardizedMessages.py +64 -0
- flexCommunicator/clientLibraries/flcpy/messages/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/node.py +486 -0
- flexCommunicator/clientLibraries/flcpy/security/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/security/security.py +189 -0
- flexCommunicator/clientLibraries/flcpy/service/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/service/client.py +58 -0
- flexCommunicator/clientLibraries/flcpy/service/service.py +67 -0
- flexCommunicator/clientLibraries/flcpy/transport/PropertyBinding.py +108 -0
- flexCommunicator/clientLibraries/flcpy/transport/PropertyRegistry.py +142 -0
- flexCommunicator/clientLibraries/flcpy/transport/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/utils/Timer.py +22 -0
- flexCommunicator/clientLibraries/flcpy/utils/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/utils/auxiliary.py +261 -0
- flexCommunicator/clientLibraries/flcpy/utils/constants.py +276 -0
- flexCommunicator/clientLibraries/flcpy/utils/decorators.py +70 -0
- flexCommunicator/clientLibraries/flcpy/validityFrame/__init__.py +0 -0
- flexCommunicator/clientLibraries/flcpy/validityFrame/runtimeMonitor.py +101 -0
- flexCommunicator/clientLibraries/flcpy/validityFrame/validityFrameManager.py +958 -0
- flexcommunicator-0.1.0.dist-info/METADATA +568 -0
- flexcommunicator-0.1.0.dist-info/RECORD +53 -0
- flexcommunicator-0.1.0.dist-info/WHEEL +5 -0
- flexcommunicator-0.1.0.dist-info/licenses/LICENSE +201 -0
- 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)
|